Compare commits

..
Author SHA1 Message Date
ohif-bot cafb7d3f7b chore(release): publish [skip ci]
- @ohif/viewer@4.9.22
2021-08-19 13:05:04 +00:00
matthiasg 5f92f6ec7f fix: Remove internet dependency for workbox (addressing #2109) (#2151)
* Ignoring *.js.map files as generated in the extensions directories for example

* Fix for #2109. Removed internet dependency to google workbox
2021-08-19 15:00:20 +02:00
Matthias SchmiederandMatthias Schmieder eea610dfe8 fix CVE-2019-5021 caused by nginx-apline (#2514)
Co-authored-by: Matthias Schmieder <matthias.schmieder@stryker.com>
2021-08-19 13:42:04 +02:00
Erik Ziegler e8733a9bd9 Update CITATION.cff (#2504) 2021-08-04 17:16:35 +02:00
Erik Ziegler d292931073 Create CITATION.cff (#2503) 2021-08-04 15:43:10 +02:00
ohif-bot 4cf17185da chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.10
 - @ohif/viewer@4.9.21
2021-07-26 11:44:06 +00:00
b8ae92c2d4 fix: changed new image handler conditional for 0 edge case (#2338)
Co-authored-by: Michael Andersen <Michael.Andersen@rmp.uhn.ca>
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2021-07-26 13:35:37 +02:00
Woonchan Cho bad67020cc chore(#2483) make client-side code debuggable right from the source code (#2484) 2021-07-26 13:34:55 +02:00
ohif-bot 2afcd498f3 chore(release): publish [skip ci]
- @ohif/extension-debugging@0.2.2
 - @ohif/extension-dicom-tag-browser@0.2.11
 - @ohif/extension-vtk@1.11.14
 - @ohif/core@2.13.13
 - @ohif/viewer@4.9.20
2021-06-03 20:56:43 +00:00
Igor Octaviano ce4c3d5360 Merge pull request #2408 from OHIF/feat/idc-2374
IDC-2374: Exponential backoff and retry after 500 error
2021-06-03 17:52:49 -03:00
igoroctaviano d575e36879 Expose retryable option 2021-06-03 17:24:39 -03:00
igoroctaviano 98fcabfcb5 Resolve opening connection before previsouly setting headers 2021-06-03 17:05:45 -03:00
Davide Punzo 1e2773345a Merge branch 'feat/idc-2374' of https://github.com/OHIF/Viewers into feat/idc-2374 2021-06-03 16:25:29 +02:00
Davide Punzo f847633cf3 update dicomweb-client to version 0.8.1 2021-06-03 16:25:06 +02:00
igoroctaviano bd2ddd7702 Remove metadata check 2021-06-03 07:20:49 -03:00
igoroctaviano 26b7b34bd4 Add check for metadata 2021-06-03 07:10:33 -03:00
Davide Punzo 6f3e446a79 Merge branch 'master' into feat/idc-2374 2021-06-03 10:11:45 +02:00
ohif-bot f0d7f2e6c7 chore(release): publish [skip ci]
- @ohif/viewer@4.9.19
2021-06-03 08:07:04 +00:00
Davide Punzo c8f7abfd53 Merge branch 'master' into feat/idc-2374 2021-06-03 10:04:02 +02:00
Davide Punzo 2b29ff1820 clean back default config (#2438) 2021-06-03 10:03:49 +02:00
Davide Punzo 5627e9c919 update dicomweb-client to version 0.8.0 2021-06-03 09:37:57 +02:00
Davide Punzo 582b713b84 Merge branch 'master' into feat/idc-2374 2021-06-03 09:25:50 +02:00
ohif-bot 5eb8e58b65 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.9
 - @ohif/extension-dicom-html@1.3.10
 - @ohif/extension-dicom-rt@0.6.13
 - @ohif/extension-dicom-segmentation@0.6.10
 - @ohif/extension-dicom-tag-browser@0.2.10
 - @ohif/extension-vtk@1.11.13
 - @ohif/core@2.13.12
 - @ohif/viewer@4.9.18
2021-06-02 14:01:32 +00:00
Igor Octaviano 403688b18c fix(dicom-html): Add parsed dicom meta info section (#2419)
* Add dicom meta info section to dicom sr html
* Update dependency: dcmjs@0.18.8
2021-06-02 15:57:56 +02:00
ohif-bot f0d13a7acd chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.8
 - @ohif/extension-dicom-rt@0.6.12
 - @ohif/extension-dicom-segmentation@0.6.9
 - @ohif/extension-vtk@1.11.12
 - @ohif/core@2.13.11
 - @ohif/ui@1.9.2
 - @ohif/viewer@4.9.17
2021-05-17 16:51:58 +00:00
Davide Punzo 78f1a7f3e5 Re #2259: UI refinements for warnings (#2397)
* Re #2259: highlight series thumbnails border for active series (series in the active viewport)
* Re #2259: add the warning icon also on the active viewport as an overlay
2021-05-17 18:48:32 +02:00
ohif-bot a57ab4d12a chore(release): publish [skip ci]
- @ohif/viewer@4.9.16
2021-05-14 01:18:42 +00:00
Igor Octaviano 3b6c6374e3 Merge pull request #2409 from OHIF/feat/add-option-to-disable-servers-cache
IDC: Add option to disable local storage of servers
2021-05-13 22:15:59 -03:00
igoroctaviano 4b508b0c25 Update config property 2021-05-13 21:56:19 -03:00
igoroctaviano 1cbef867ce Add option to disable local storage cache of servers 2021-05-13 21:42:46 -03:00
igoroctaviano 05444f48d4 Improve docs remove env variables 2021-05-13 09:30:52 -03:00
igoroctaviano 2ea227ca06 Extract hook and add config and documentation 2021-05-13 09:06:11 -03:00
igoroctaviano 35aaad175c Update naming 2021-05-13 08:11:09 -03:00
igoroctaviano 863fe05907 Update naming 2021-05-13 08:11:03 -03:00
igoroctaviano 1c9560044f Implement handlers to retry metadata requests 2021-05-13 08:10:53 -03:00
ohif-bot 343f06c57d chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.3.9
 - @ohif/extension-dicom-rt@0.6.11
 - @ohif/extension-dicom-segmentation@0.6.8
 - @ohif/extension-dicom-tag-browser@0.2.9
 - @ohif/extension-vtk@1.11.11
 - @ohif/core@2.13.10
 - @ohif/viewer@4.9.15
2021-04-22 03:42:37 +00:00
dannyrb e2bb647386 Merge branch 'master' of github.com:OHIF/Viewers into ups-master 2021-04-21 23:29:54 -04:00
dannyrb a3cb9dab57 remove sitemap dep from older version 2021-04-21 23:29:41 -04:00
ohif-bot 410ab028c7 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.3.8
 - @ohif/extension-dicom-rt@0.6.10
 - @ohif/extension-dicom-segmentation@0.6.7
 - @ohif/extension-dicom-tag-browser@0.2.8
 - @ohif/extension-vtk@1.11.10
 - @ohif/core@2.13.9
 - @ohif/viewer@4.9.14
2021-04-22 03:27:52 +00:00
dannyrb 54dd738f61 Merge branch 'master' of github.com:OHIF/Viewers into ups-master 2021-04-21 23:24:39 -04:00
dannyrb 6f16c7e48d Remove broken plugin? Seems to have broken child dep 2021-04-21 23:24:28 -04:00
ohif-bot 82ddd95cf0 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.3.7
 - @ohif/extension-dicom-rt@0.6.9
 - @ohif/extension-dicom-segmentation@0.6.6
 - @ohif/extension-dicom-tag-browser@0.2.7
 - @ohif/extension-vtk@1.11.9
 - @ohif/core@2.13.8
 - @ohif/viewer@4.9.13
2021-04-21 14:14:00 +00:00
Davide Punzo 2b658fae7e IDC2345: update dcmjs to version 0.18.5 (#2378) 2021-04-21 16:10:49 +02:00
ohif-bot a822151e92 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.3.6
 - @ohif/extension-vtk@1.11.8
 - @ohif/core@2.13.7
 - @ohif/viewer@4.9.12
2021-04-21 11:17:09 +00:00
Igor Octaviano 0ae49c6fdc IDC-2348: Add acquisition storage sr sopclass to sr html ext (#2352)
* Add acquisition context storage sr sop to html ext
2021-04-21 13:12:54 +02:00
ohif-bot 1d069d2da3 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.6.5
 - @ohif/extension-vtk@1.11.7
 - @ohif/core@2.13.6
 - @ohif/viewer@4.9.11
2021-04-21 10:22:55 +00:00
Igor Octaviano 274342a5e1 Merge pull request #2369 from Punzo/IDC2361
Re #2361: fix seg combobox filling
2021-04-21 07:19:32 -03:00
Davide Punzo 21a5d1bfc4 update 2021-04-21 10:40:02 +02:00
Davide Punzo e421bc7df9 update 2021-04-20 16:39:04 +02:00
Davide Punzo e41ba2bfde IDC2361: fix seg combobox filling 2021-04-19 19:42:30 +02:00
ohif-bot d2e75b0f20 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.6.4
 - @ohif/extension-vtk@1.11.6
 - @ohif/core@2.13.5
 - @ohif/viewer@4.9.10
2021-04-16 18:18:27 +00:00
Davide Punzo 07b272e024 IDC2345: implement thumbnails footnotes UI for segmentation series inconsistencies warning (#2364)
* IDC2345: implement thumbnails footnotes UI for segmentation series inconsistencies warning
2021-04-16 20:14:04 +02:00
ohif-bot a08caa9bdb chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.7
 - @ohif/extension-dicom-html@1.3.5
 - @ohif/extension-dicom-rt@0.6.8
 - @ohif/extension-dicom-segmentation@0.6.3
 - @ohif/extension-dicom-tag-browser@0.2.6
 - @ohif/extension-vtk@1.11.5
 - @ohif/core@2.13.4
 - @ohif/viewer@4.9.9
2021-04-15 13:13:50 +00:00
Igor Octaviano 29afddb3a6 Merge pull request #2363 from OHIF/fix/window-level-issues
fix(MetadataProvider): Update metadata provider to resolve issues after dicomweb update/new return value
2021-04-15 10:10:48 -03:00
igoroctaviano f85f856734 Comment cache 2021-04-15 09:40:37 -03:00
igoroctaviano c6bc19c002 Rollback circle ci test 2021-04-15 09:29:50 -03:00
igoroctaviano 538555f000 Merge branch 'master' of github.com:OHIF/Viewers into fix/window-level-issues 2021-04-15 09:17:54 -03:00
igoroctaviano 8681036b21 fix(wl): fix metadata provider window level issues after dicomweb update (string vs number) 2021-04-15 09:15:42 -03:00
Davide Punzo e55b909ca6 IDC2345: Update dcmjs to version 0.18.4 (#2362) 2021-04-15 13:33:13 +02:00
Davide Punzo 4bb36704ee IDC2346: fix images sorting (#2358) 2021-04-14 19:43:58 +02:00
Davide Punzo 9bbdb35d88 IDC2259: do not check uniform spacing in the 4th dimension for 4D datasets (#2356) 2021-04-13 12:57:28 +02:00
qqalexqq 2f443fa2ef fix: Update Dockerfile node version - fix missing dependencies (#2349)
There were some utilities missing: python, make, g++. Also node version was too old - current node builds without problems.
2021-04-05 09:47:57 +02:00
ohif-bot 6d7d7da287 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.6.2
 - @ohif/viewer@4.9.8
2021-03-31 10:24:08 +00:00
Davide Punzo 1f6751f95b IDC2332 (#2341)
1) fix the loading of the segmentations when filtering study with ?seriesInstanceUID= syntax
2) add time in the dates of the items of the segmentation combobox list
3) order segmentation combobox list in reverse time order
2021-03-31 12:20:34 +02:00
ohif-bot 92081a097d chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.6
 - @ohif/viewer@4.9.7
2021-03-26 18:38:58 +00:00
Igor Octaviano ba1a33c3ac Merge pull request #2335 from OHIF/idc-2334
IDC-2334: Resolve choppy scrolling in 2D mode
2021-03-26 15:36:07 -03:00
igoroctaviano 193ff12b6e Fix scroll speed 2021-03-26 13:27:51 -03:00
ohif-bot 3296ffaa07 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.11.4
 - @ohif/core@2.13.3
 - @ohif/ui@1.9.1
 - @ohif/viewer@4.9.6
2021-03-26 10:35:50 +00:00
Davide Punzo 0456d92462 IDC 2259: add warning UI for series inconsistencies (#2331)
IDC 2259: add warning UI for series inconsistencies

remove VTKMPRToolbarButton component memoization (it is not anymore necessary)
2021-03-26 11:32:10 +01:00
ohif-bot db3b15e537 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.11.3
 - @ohif/core@2.13.2
 - @ohif/viewer@4.9.5
2021-03-25 16:16:14 +00:00
Igor OctavianoandDavide Punzo 6e723ebced fix: IDC-2288: Update toolbar tabs after derived displayset loaded (#2307)
* Update toolbar tabs after derived displayset loaded

* trigger CI

Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2021-03-25 17:12:20 +01:00
ohif-bot a6a231c81e chore(release): publish [skip ci]
- @ohif/extension-debugging@0.2.1
 - @ohif/viewer@4.9.4
2021-03-19 18:54:21 +00:00
Igor Octaviano 7c2bd9b76a Merge pull request #2321 from OHIF/bugfix/idc-2006
bug(IDC-2006): Fix bug introduced with new logger service which broke debugger mailto functionality
2021-03-19 15:51:01 -03:00
igoroctaviano 5dfa8d465d Force pipeline 2021-03-19 15:12:37 -03:00
igoroctaviano fc5301e904 bug(debugger):Fix bug that breaks mailto introduced by logger service 2021-03-18 07:40:59 -03:00
ohif-bot 88e5a7e54a chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.5
 - @ohif/viewer@4.9.3
2021-03-15 20:12:36 +00:00
Igor Octaviano 5d24bb4b73 Merge pull request #2315 from OHIF/igoroctaviano/react-cornerstone-viewport-4.0.5
Update dependency: react-cornerstone-viewport@4.0.5
2021-03-15 17:09:16 -03:00
igoroctaviano 03a4056179 Update dependency: react-cornerstone-viewport@4.0.5
react-cornerstone-viewport
  * @ohif/extension-cornerstone: 4.0.4 → 4.0.5
2021-03-15 16:42:24 -03:00
ohif-bot ec76bc3b0b chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.4
 - @ohif/extension-dicom-html@1.3.4
 - @ohif/extension-dicom-rt@0.6.7
 - @ohif/extension-dicom-segmentation@0.6.1
 - @ohif/extension-dicom-tag-browser@0.2.5
 - @ohif/extension-vtk@1.11.2
 - @ohif/core@2.13.1
 - @ohif/viewer@4.9.2
2021-03-09 19:43:21 +00:00
Davide Punzo b1070acaca Update dependency: dcmjs@0.18.3 (#2302) 2021-03-09 20:38:19 +01:00
ohif-bot 7b5beb8f98 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.11.1
 - @ohif/viewer@4.9.1
2021-03-09 15:55:47 +00:00
97410e12b9 Added memoization on VTKMPRToolbarButton component to avoid unnecesary rebuilds (#2274)
* Added memoization on VTKMPRToolbarButton component to avoid unnecessary rebuilds

* update isDisplaySetReconstructable for float precision

Co-authored-by: Rodrigo Cáceres Vicencio <rcaceres@nursoft.cl>
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2021-03-09 16:52:56 +01:00
ohif-bot bdecbfeee3 chore(release): publish [skip ci]
- @ohif/extension-debugging@0.2.0
 - @ohif/extension-dicom-microscopy@0.52.0
 - @ohif/extension-dicom-segmentation@0.6.0
 - @ohif/extension-vtk@1.11.0
 - @ohif/core@2.13.0
 - @ohif/ui@1.9.0
 - @ohif/viewer@4.9.0
2021-03-04 21:44:04 +00:00
Igor Octaviano 5a970056ae Merge pull request #2283 from OHIF/IDC-2223-update
feat(IDC-2223): add new log service to be used by debugger extension
2021-03-04 18:41:04 -03:00
igoroctaviano 8581405352 Add logger to viewport grid 2021-03-04 18:20:57 -03:00
igoroctaviano df8c4db2ed Update broken test 2021-03-03 20:06:58 -03:00
igoroctaviano ea62bdaddf Merge branch 'master' of github.com:OHIF/Viewers into IDC-2223-update 2021-03-03 19:18:36 -03:00
igoroctaviano e983b02677 Add onerror event to cover all other unhandled errors 2021-03-03 19:16:49 -03:00
ohif-bot 1e4b78d78d chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.3
 - @ohif/extension-dicom-html@1.3.3
 - @ohif/extension-dicom-rt@0.6.6
 - @ohif/extension-dicom-segmentation@0.5.4
 - @ohif/extension-dicom-tag-browser@0.2.4
 - @ohif/extension-vtk@1.10.6
 - @ohif/core@2.12.5
 - @ohif/viewer@4.8.12
2021-03-03 18:31:51 +00:00
igoroctaviano ee6131f98e Merge branch 'master' of github.com:OHIF/Viewers into IDC-2223-update 2021-03-03 15:28:23 -03:00
Igor Octaviano a56bdd035b Merge pull request #2290 from OHIF/bump-dcmjs
Update dependency: dcmjs@0.18.2
2021-03-03 15:27:54 -03:00
igoroctaviano 04901c296e Update dependency: dcmjs@0.18.2
dcmjs
  * @ohif/core: 0.18.1 → 0.18.2
  * @ohif/extension-cornerstone: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-html: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-rt: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-segmentation: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-tag-browser: 0.18.1 → 0.18.2
  * @ohif/extension-vtk: 0.18.1 → 0.18.2
  * @ohif/viewer: 0.18.1 → 0.18.2
2021-03-03 14:35:06 -03:00
ohif-bot 59cadad98c chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.2
 - @ohif/extension-dicom-html@1.3.2
 - @ohif/extension-dicom-rt@0.6.5
 - @ohif/extension-dicom-segmentation@0.5.3
 - @ohif/extension-dicom-tag-browser@0.2.3
 - @ohif/extension-vtk@1.10.5
 - @ohif/core@2.12.4
 - @ohif/viewer@4.8.11
2021-02-25 21:31:47 +00:00
igoroctaviano 14d6454eaf feat(log): add new log service 2021-02-25 18:29:28 -03:00
Igor Octaviano 64f87d2c93 Merge pull request #2281 from OHIF/fix/bump-dcmjs
Update dependency: dcmjs@0.18.1
2021-02-25 18:28:37 -03:00
igoroctaviano cfd66e5ef7 Update dependency: dcmjs@0.18.1
dcmjs
  * @ohif/core: 0.17.2 → 0.18.1
  * @ohif/extension-cornerstone: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-html: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-rt: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-segmentation: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-tag-browser: 0.17.2 → 0.18.1
  * @ohif/extension-vtk: 0.17.2 → 0.18.1
  * @ohif/viewer: 0.17.2 → 0.18.1
2021-02-25 17:05:24 -03:00
Andrey Fedorovandigoroctaviano 6480389778 chore: add acknowledgment of IDC (#2255)
* add acknowldgment of IDC

resolves #1136

* Fix typo + remove IDC ack example

Co-authored-by: igoroctaviano <igoroctaviano@gmail.com>
2021-02-12 12:08:43 +01:00
ohif-bot 0e736e7a56 chore(release): publish [skip ci]
- @ohif/viewer@4.8.10
2021-02-08 11:56:46 +00:00
Igor Octaviano d246cd9842 Merge pull request #2267 from OHIF/IDC-2251
IDC-2251: Add error page and not found pages if failed to retrieve study data
2021-02-08 08:53:35 -03:00
igoroctaviano 4fa1eb683d Merge branch 'master' of github.com:OHIF/Viewers into IDC-2251 2021-02-07 22:29:45 -03:00
igoroctaviano d97270f995 Merge branch 'master' of github.com:OHIF/Viewers into IDC-2251 2021-02-05 17:00:06 -03:00
igoroctaviano 7c5f9c44b6 Add error page and not found pages if failed to retrieve study data 2021-02-03 13:58:46 -03:00
164 changed files with 14007 additions and 513 deletions

No files matched your search

+35 -27
View File
@@ -48,12 +48,12 @@ jobs:
- checkout: - checkout:
post: post:
- git fetch --all - git fetch --all
- restore_cache: # - restore_cache:
name: Restore Yarn and Cypress Package Cache # name: Restore Yarn and Cypress Package Cache
keys: # keys:
# when lock file changes, use increasingly general patterns to restore cache # # when lock file changes, use increasingly general patterns to restore cache
- yarn-packages-{{ checksum "yarn.lock" }} # - yarn-packages-{{ checksum "yarn.lock" }}
- yarn-packages- # - yarn-packages-
- run: - run:
name: Install Dependencies name: Install Dependencies
command: yarn install --frozen-lockfile command: yarn install --frozen-lockfile
@@ -107,12 +107,12 @@ jobs:
post: post:
- git fetch --all - git fetch --all
- restore_cache: # - restore_cache:
name: Restore Yarn and Cypress Package Cache # name: Restore Yarn and Cypress Package Cache
keys: # keys:
# when lock file changes, use increasingly general patterns to restore cache # # when lock file changes, use increasingly general patterns to restore cache
- yarn-packages-{{ checksum "yarn.lock" }} # - yarn-packages-{{ checksum "yarn.lock" }}
- yarn-packages- # - yarn-packages-
- run: - run:
name: Install Dependencies name: Install Dependencies
@@ -143,12 +143,12 @@ jobs:
- checkout: - checkout:
post: post:
- git fetch --all - git fetch --all
- restore_cache: # - restore_cache:
name: Restore Yarn and Cypress Package Cache # name: Restore Yarn and Cypress Package Cache
keys: # keys:
# when lock file changes, use increasingly general patterns to restore cache # # when lock file changes, use increasingly general patterns to restore cache
- yarn-packages-{{ checksum "yarn.lock" }} # - yarn-packages-{{ checksum "yarn.lock" }}
- yarn-packages- # - yarn-packages-
- run: - run:
name: Install Dependencies name: Install Dependencies
command: yarn install --frozen-lockfile command: yarn install --frozen-lockfile
@@ -237,11 +237,11 @@ jobs:
post: post:
- git fetch --all - git fetch --all
# Use increasingly general patterns to restore cache # Use increasingly general patterns to restore cache
- restore_cache: # - restore_cache:
name: Restore Yarn and Cypress Package Cache # name: Restore Yarn and Cypress Package Cache
keys: # keys:
- yarn-packages-{{ checksum "yarn.lock" }} # - yarn-packages-{{ checksum "yarn.lock" }}
- yarn-packages- # - yarn-packages-
- run: - run:
name: Install Dependencies name: Install Dependencies
command: yarn install --frozen-lockfile command: yarn install --frozen-lockfile
@@ -346,7 +346,9 @@ workflows:
record: true record: true
store_artifacts: true store_artifacts: true
working_directory: platform/viewer working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
yarn run build
start: yarn run test:e2e:serve start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*' spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
wait-on: 'http://localhost:3000' wait-on: 'http://localhost:3000'
@@ -372,7 +374,9 @@ workflows:
record: true record: true
store_artifacts: true store_artifacts: true
working_directory: platform/viewer working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build:package build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
yarn run build:package
start: yarn run test:e2e:serve start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*' spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
wait-on: 'http://localhost:3000' wait-on: 'http://localhost:3000'
@@ -403,7 +407,9 @@ workflows:
yarn: true yarn: true
store_artifacts: false store_artifacts: false
working_directory: platform/viewer working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
yarn run build
# start server --> verify running --> percy + chrome + cypress # start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
@@ -480,7 +486,9 @@ workflows:
yarn: true yarn: true
store_artifacts: false store_artifacts: false
working_directory: platform/viewer working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
yarn run build
# start server --> verify running --> percy + chrome + cypress # start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
+2
View File
@@ -9,6 +9,7 @@ src/version.js
junit.xml junit.xml
coverage/ coverage/
.docz/ .docz/
*.js.map
# YALC (for Erik) # YALC (for Erik)
.yalc .yalc
@@ -34,3 +35,4 @@ screenshots/
# Locize settings # Locize settings
.locize .locize
+15
View File
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
+24
View File
@@ -0,0 +1,24 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Ziegler"
given-names: "Erik"
orcid: "https://orcid.org/0000-0003-1857-8129"
- family-names: "Urban"
given-names: "Trinity"
- family-names: "Brown"
given-names: "Danny"
- family-names: "Petts"
given-names: "James"
- family-names: "Pieper"
given-names: "Steve D"
- family-names: "Lewis"
given-names: "Rob"
- family-names: "Hafey"
given-names: "Chris"
- family-names: "Harris"
given-names: "Gordon J"
title: "Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework for Building Web-Based Imaging Applications to Support Cancer Research"
doi: 10.1200/CCI.19.00131
version: 1.0.0
url: "https://github.com/OHIF/Viewers"
+3 -2
View File
@@ -21,7 +21,7 @@
# Stage 1: Build the application # Stage 1: Build the application
# docker build -t ohif/viewer:latest . # docker build -t ohif/viewer:latest .
FROM node:10.16.3-slim as builder FROM node:15.13.0-slim as builder
RUN mkdir /usr/src/app RUN mkdir /usr/src/app
WORKDIR /usr/src/app WORKDIR /usr/src/app
@@ -39,6 +39,7 @@ COPY package.json /usr/src/app/package.json
COPY postcss.config.js /usr/src/app/postcss.config.js COPY postcss.config.js /usr/src/app/postcss.config.js
COPY yarn.lock /usr/src/app/yarn.lock COPY yarn.lock /usr/src/app/yarn.lock
RUN apt-get update && apt-get install -y python make g++
# Run the install before copying the rest of the files # Run the install before copying the rest of the files
RUN yarn config set workspaces-experimental true RUN yarn config set workspaces-experimental true
RUN yarn install RUN yarn install
@@ -52,7 +53,7 @@ RUN yarn run build
# Stage 2: Bundle the built application into a Docker container # Stage 2: Bundle the built application into a Docker container
# which runs Nginx using Alpine Linux # which runs Nginx using Alpine Linux
FROM nginx:1.15.5-alpine FROM nginx:1.21.1-alpine
RUN apk add --no-cache bash RUN apk add --no-cache bash
RUN rm -rf /etc/nginx/conf.d RUN rm -rf /etc/nginx/conf.d
COPY .docker/Viewer-v2.x /etc/nginx/conf.d COPY .docker/Viewer-v2.x /etc/nginx/conf.d
+16 -3
View File
@@ -248,9 +248,6 @@ To acknowledge the OHIF Viewer in an academic publication, please cite
> >
> _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI: > _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI:
> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) > [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334)
>
> NCI Imaging Data Commons: contract number 19X037Q from Leidos Biomedical
> Research under Task Order HHSN26100071 from NCI.
**Note:** If you use or find this repository helpful, please take the time to **Note:** If you use or find this repository helpful, please take the time to
star this repository on Github. This is an easy way for us to assess adoption star this repository on Github. This is an easy way for us to assess adoption
@@ -261,6 +258,22 @@ Cancer Institute, Informatics Technology for Cancer Research (ITCR) program,
under a under a
[grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104). [grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104).
## Projects that contributed to OHIF Viewer
The following is a (partial) list of projects that contributed resources towards
development of OHIF Viewer:
- [NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/)
supported development of new features and bug fixes marked with
["IDC:priority"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Apriority),
["IDC:candidate"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acandidate)
or
["IDC:collaboration"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acollaboration).
NCI Imaging Data Commons is supported by the contract number 19X037Q from
Leidos Biomedical Research under Task Order HHSN26100071 from NCI.
[IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized
version of the OHIF Viewer.
## License ## License
MIT © [OHIF](https://github.com/OHIF) MIT © [OHIF](https://github.com/OHIF)
+2 -2
View File
@@ -8,7 +8,6 @@
"-fontsettings", "-fontsettings",
"github", "github",
"ga", "ga",
"sitemap",
"anchors", "anchors",
"versions" "versions"
], ],
@@ -24,7 +23,8 @@
"token": "UA-110573590-2" "token": "UA-110573590-2"
}, },
"sitemap": { "sitemap": {
"hostname": "https://docs.ohif.org" "hostname": "https://docs.ohif.org",
"todo": "sitemap plugin removed; causing build errors"
}, },
"versions": { "versions": {
"gitbookConfigURL": "https://raw.githubusercontent.com/OHIF/Viewers/master/docs/book.json", "gitbookConfigURL": "https://raw.githubusercontent.com/OHIF/Viewers/master/docs/book.json",
+2 -2
View File
@@ -8,7 +8,6 @@
"-fontsettings", "-fontsettings",
"github", "github",
"ga", "ga",
"sitemap",
"anchors", "anchors",
"versions" "versions"
], ],
@@ -24,7 +23,8 @@
"token": "UA-110573590-2" "token": "UA-110573590-2"
}, },
"sitemap": { "sitemap": {
"hostname": "https://docs.ohif.org" "hostname": "https://docs.ohif.org",
"todo": "sitemap plugin removed; causing build errors"
}, },
"versions": { "versions": {
"gitbookConfigURL": "https://raw.githubusercontent.com/OHIF/Viewers/master/docs/book.json", "gitbookConfigURL": "https://raw.githubusercontent.com/OHIF/Viewers/master/docs/book.json",
+78
View File
@@ -3,6 +3,84 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.10.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.9...@ohif/extension-cornerstone@2.10.10) (2021-07-26)
### Bug Fixes
* changed new image handler conditional for 0 edge case ([#2338](https://github.com/OHIF/Viewers/issues/2338)) ([b8ae92c](https://github.com/OHIF/Viewers/commit/b8ae92c2d4f733b3ca89826118029685650b396a))
## [2.10.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.8...@ohif/extension-cornerstone@2.10.9) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [2.10.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.7...@ohif/extension-cornerstone@2.10.8) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.6...@ohif/extension-cornerstone@2.10.7) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.5...@ohif/extension-cornerstone@2.10.6) (2021-03-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.4...@ohif/extension-cornerstone@2.10.5) (2021-03-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.3...@ohif/extension-cornerstone@2.10.4) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.2...@ohif/extension-cornerstone@2.10.3) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.1...@ohif/extension-cornerstone@2.10.2) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.0...@ohif/extension-cornerstone@2.10.1) (2021-02-05) ## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.0...@ohif/extension-cornerstone@2.10.1) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-cornerstone **Note:** Version bump only for package @ohif/extension-cornerstone
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.10.1", "version": "2.10.10",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -36,7 +36,7 @@
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
@@ -51,6 +51,6 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "4.0.4" "react-cornerstone-viewport": "4.0.5"
} }
} }
@@ -1,5 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay'
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport'; import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -194,6 +195,7 @@ class OHIFCornerstoneViewport extends Component {
return null; return null;
} }
const { viewportIndex } = this.props; const { viewportIndex } = this.props;
const { inconsistencyWarnings } = this.props.viewportData.displaySet;
const { const {
imageIds, imageIds,
currentImageIdIndex, currentImageIdIndex,
@@ -219,7 +221,7 @@ class OHIFCornerstoneViewport extends Component {
const { displaySet } = this.props.viewportData; const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet; const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) { if (currentImageIdIndex >= 0) {
this.props.onNewImage({ this.props.onNewImage({
StudyInstanceUID, StudyInstanceUID,
SOPInstanceUID: sopInstanceUid, SOPInstanceUID: sopInstanceUid,
@@ -229,14 +231,19 @@ class OHIFCornerstoneViewport extends Component {
} }
}; };
const warningsOverlay = props => {
return <OHIFCornerstoneViewportOverlay {...props} inconsistencyWarnings={inconsistencyWarnings} />
};
return ( return (
<> <>
<ConnectedCornerstoneViewport <ConnectedCornerstoneViewport
viewportIndex={viewportIndex} viewportIndex={viewportIndex}
imageIds={imageIds} imageIds={imageIds}
imageIdIndex={currentImageIdIndex} imageIdIndex={currentImageIdIndex}
onNewImage={newImageHandler} onNewImageDebounced={newImageHandler}
onNewImageDebounceTime={700} onNewImageDebounceTime={300}
viewportOverlayComponent={warningsOverlay}
// ~~ Connected (From REDUX) // ~~ Connected (From REDUX)
// frameRate={frameRate} // frameRate={frameRate}
// isPlaying={false} // isPlaying={false}
+8 -1
View File
@@ -268,6 +268,7 @@ const commandsModule = ({ servicesManager }) => {
SOPInstanceUID, SOPInstanceUID,
frameIndex, frameIndex,
activeViewportIndex, activeViewportIndex,
refreshViewports = true,
}) => { }) => {
const study = studyMetadataManager.get(StudyInstanceUID); const study = studyMetadataManager.get(StudyInstanceUID);
@@ -278,6 +279,10 @@ const commandsModule = ({ servicesManager }) => {
); );
}); });
if (!displaySet) {
return;
}
displaySet.SOPInstanceUID = SOPInstanceUID; displaySet.SOPInstanceUID = SOPInstanceUID;
displaySet.frameIndex = frameIndex; displaySet.frameIndex = frameIndex;
@@ -285,7 +290,9 @@ const commandsModule = ({ servicesManager }) => {
setViewportSpecificData(activeViewportIndex, displaySet) setViewportSpecificData(activeViewportIndex, displaySet)
); );
refreshCornerstoneViewports(); if (refreshViewports) {
refreshCornerstoneViewports();
}
}, },
}; };
@@ -0,0 +1,69 @@
.imageViewerViewport.empty ~ .OHIFCornerstoneViewportOverlay {
display: none;
}
.OHIFCornerstoneViewportOverlay {
color: #9ccef9;
}
.OHIFCornerstoneViewportOverlay .overlay-element {
position: absolute;
font-weight: 400;
text-shadow: 1px 1px #000;
pointer-events: none;
}
.OHIFCornerstoneViewportOverlay .top-left {
top: 20px;
left: 20px;
}
.OHIFCornerstoneViewportOverlay .top-center {
top: 20px;
padding-top: 20px;
width: 100%;
text-align: center;
}
.OHIFCornerstoneViewportOverlay .top-right {
top: 20px;
right: 20px;
text-align: right;
}
.OHIFCornerstoneViewportOverlay .bottom-left {
bottom: 20px;
left: 20px;
}
.OHIFCornerstoneViewportOverlay .bottom-left2 {
bottom: 140px;
left: 20px;
}
.OHIFCornerstoneViewportOverlay .bottom-right {
bottom: 20px;
right: 20px;
text-align: right;
}
.OHIFCornerstoneViewportOverlay.controlsVisible .topright,
.OHIFCornerstoneViewportOverlay.controlsVisible .bottomright {
right: calc(20px + 19px);
}
.OHIFCornerstoneViewportOverlay .warning {
position: absolute;
font-weight: 400;
text-shadow: 1px 1px #000;
pointer-events: inherit;
}
.OHIFCornerstoneViewportOverlay .warning svg {
opacity: 1;
color: #e29e4a;
fill: #e29e4a;
stroke: #e29e4a;
width: 32px;
height: 28px;
}
.OHIFCornerstoneViewportOverlay svg {
color: #9ccef9;
fill: #9ccef9;
stroke: #9ccef9;
background-color: transparent;
margin: 2px;
width: 18px;
height: 18px;
}
@@ -0,0 +1,167 @@
import { PureComponent } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import './OHIFCornerstoneViewportOverlay.css';
import {
isValidNumber,
formatNumberPrecision,
formatDICOMDate,
formatDICOMTime,
formatPN,
getCompression
} from '../utils/formatStudy';
import classNames from 'classnames';
import { Icon } from '@ohif/ui/src/elements/Icon';
import { Tooltip } from '@ohif/ui/src/components/tooltip';
import { OverlayTrigger } from '@ohif/ui/src/components/overlayTrigger';
class OHIFCornerstoneViewportOverlay extends PureComponent {
static propTypes = {
scale: PropTypes.number.isRequired,
windowWidth: PropTypes.oneOfType([
PropTypes.number.isRequired,
PropTypes.string.isRequired,
]),
windowCenter: PropTypes.oneOfType([
PropTypes.number.isRequired,
PropTypes.string.isRequired,
]),
imageId: PropTypes.string.isRequired,
imageIndex: PropTypes.number.isRequired,
stackSize: PropTypes.number.isRequired,
inconsistencyWarnings: PropTypes.array.isRequired
};
render() {
const { imageId, scale, windowWidth, windowCenter, inconsistencyWarnings } = this.props;
if (!imageId) {
return null;
}
const zoomPercentage = formatNumberPrecision(scale * 100, 0);
const seriesMetadata =
cornerstone.metaData.get('generalSeriesModule', imageId) || {};
const imagePlaneModule =
cornerstone.metaData.get('imagePlaneModule', imageId) || {};
const { rows, columns, sliceThickness, sliceLocation } = imagePlaneModule;
const { seriesNumber, seriesDescription } = seriesMetadata;
const generalStudyModule =
cornerstone.metaData.get('generalStudyModule', imageId) || {};
const { studyDate, studyTime, studyDescription } = generalStudyModule;
const patientModule =
cornerstone.metaData.get('patientModule', imageId) || {};
const { patientId, patientName } = patientModule;
const generalImageModule =
cornerstone.metaData.get('generalImageModule', imageId) || {};
const { instanceNumber } = generalImageModule;
const cineModule = cornerstone.metaData.get('cineModule', imageId) || {};
const { frameTime } = cineModule;
const frameRate = formatNumberPrecision(1000 / frameTime, 1);
const compression = getCompression(imageId);
const wwwc = `W: ${
windowWidth.toFixed ? windowWidth.toFixed(0) : windowWidth
} L: ${windowWidth.toFixed ? windowCenter.toFixed(0) : windowCenter}`;
const imageDimensions = `${columns} x ${rows}`;
const { imageIndex, stackSize } = this.props;
const inconsistencyWarningsOn = inconsistencyWarnings && inconsistencyWarnings.length !== 0 ? true : false;
const getWarningContent = (warningList) => {
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
}
};
const getWarningInfo = (seriesNumber, inconsistencyWarnings) => {
return(
<React.Fragment>
{inconsistencyWarnings.length != 0 ? (
<OverlayTrigger
key={seriesNumber}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
<div className="warningContent">{getWarningContent(inconsistencyWarnings)}</div>
</Tooltip>
}
>
<div className={classNames('warning')}>
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
};
const normal = (
<React.Fragment>
<div className="top-left overlay-element">
<div>{formatPN(patientName)}</div>
<div>{patientId}</div>
</div>
<div className="top-right overlay-element">
<div>{studyDescription}</div>
<div>
{formatDICOMDate(studyDate)} {formatDICOMTime(studyTime)}
</div>
</div>
<div className="bottom-right overlay-element">
<div>Zoom: {zoomPercentage}%</div>
<div>{wwwc}</div>
<div className="compressionIndicator">{compression}</div>
</div>
<div className="bottom-left2 warning">
<div>{inconsistencyWarningsOn ? getWarningInfo(seriesNumber, inconsistencyWarnings) : ''}</div>
</div>
<div className="bottom-left overlay-element">
<div>{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}</div>
<div>
{stackSize > 1
? `Img: ${instanceNumber} ${imageIndex}/${stackSize}`
: ''}
</div>
<div>
{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}
<div>{imageDimensions}</div>
<div>
{isValidNumber(sliceLocation)
? `Loc: ${formatNumberPrecision(sliceLocation, 2)} mm `
: ''}
{sliceThickness
? `Thick: ${formatNumberPrecision(sliceThickness, 2)} mm`
: ''}
</div>
<div>{seriesDescription}</div>
</div>
</div>
</React.Fragment>
);
return <div className="OHIFCornerstoneViewportOverlay">{normal}</div>;
}
}
export default OHIFCornerstoneViewportOverlay;
+7
View File
@@ -38,7 +38,14 @@ export default {
}, },
getViewportModule({ commandsManager }) { getViewportModule({ commandsManager }) {
const ExtendedOHIFCornerstoneViewport = props => { const ExtendedOHIFCornerstoneViewport = props => {
/**
* TODO: This appears to be used to set the redux parameters for
* the viewport when new images are loaded. It's very ugly
* and we should remove it.
*/
const onNewImageHandler = jumpData => { const onNewImageHandler = jumpData => {
/** Do not trigger all viewports to render unnecessarily */
jumpData.refreshViewports = false;
commandsManager.runCommand('jumpToImage', jumpData); commandsManager.runCommand('jumpToImage', jumpData);
}; };
return ( return (
@@ -0,0 +1,109 @@
import moment from 'moment';
/**
* Checks if value is valid.
*
* @param {number} value
* @returns {boolean} is valid.
*/
function isValidNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
/**
* Formats number precision.
*
* @param {number} number
* @param {number} precision
* @returns {number} formatted number.
*/
function formatNumberPrecision(number, precision) {
if (number !== null) {
return parseFloat(number).toFixed(precision);
}
}
/**
* Formats DICOM date.
*
* @param {string} date
* @param {string} strFormat
* @returns {string} formatted date.
*/
function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
return moment(date, 'YYYYMMDD').format(strFormat);
}
/**
* DICOM Time is stored as HHmmss.SSS, where:
* HH 24 hour time:
* m mm 0..59 Minutes
* s ss 0..59 Seconds
* S SS SSS 0..999 Fractional seconds
*
* Goal: '24:12:12'
*
* @param {*} time
* @param {string} strFormat
* @returns {string} formatted name.
*/
function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
return moment(time, 'HH:mm:ss').format(strFormat);
}
/**
* Formats a patient name for display purposes
*
* @param {string} name
* @returns {string} formatted name.
*/
function formatPN(name) {
if (!name) {
return;
}
// Convert the first ^ to a ', '. String.replace() only affects
// the first appearance of the character.
const commaBetweenFirstAndLast = name.replace('^', ', ');
// Replace any remaining '^' characters with spaces
const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' ');
// Trim any extraneous whitespace
return cleaned.trim();
}
/**
* Gets compression type
*
* @param {number} imageId
* @returns {string} comrpession type.
*/
function getCompression(imageId) {
const generalImageModule =
cornerstone.metaData.get('generalImageModule', imageId) || {};
const {
lossyImageCompression,
lossyImageCompressionRatio,
lossyImageCompressionMethod,
} = generalImageModule;
if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
const compressionRatio = formatNumberPrecision(
lossyImageCompressionRatio,
2
);
return compressionMethod + compressionRatio + ' : 1';
}
return 'Lossless / Uncompressed';
}
export { isValidNumber,
formatNumberPrecision,
formatDICOMDate,
formatDICOMTime,
formatPN,
getCompression
};
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.1...@ohif/extension-debugging@0.2.2) (2021-06-03)
**Note:** Version bump only for package @ohif/extension-debugging
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.0...@ohif/extension-debugging@0.2.1) (2021-03-19)
**Note:** Version bump only for package @ohif/extension-debugging
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.6...@ohif/extension-debugging@0.2.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06) ## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06)
**Note:** Version bump only for package @ohif/extension-debugging **Note:** Version bump only for package @ohif/extension-debugging
+3 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-debugging", "name": "@ohif/extension-debugging",
"version": "0.1.6", "version": "0.2.2",
"description": "OHIF extension for debugging.", "description": "OHIF extension for debugging.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -28,13 +28,12 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^2.6.0", "@ohif/core": "^2.6.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3"
"dicomweb-client": "^0.5.2"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"detect-browser": "5.1.1", "detect-browser": "5.1.1",
"dicomweb-client": "^0.6.0", "dicomweb-client": "^0.8.1",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"jszip": "^3.2.2" "jszip": "^3.2.2"
} }
@@ -12,3 +12,15 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.debug-report-modal-container .errors {
margin-top: 10px;
}
.debug-report-modal-container .errors-container {
border: 1px solid var(--active-color);
border-radius: 5px;
padding: 10px;
overflow: scroll;
max-height: 300px;
}
@@ -10,6 +10,7 @@ const DubugReportModal = ({
extensionManager, extensionManager,
mailTo, mailTo,
debugModalMessage, debugModalMessage,
errors = [],
}) => { }) => {
const copyDebugDataToClipboard = () => { const copyDebugDataToClipboard = () => {
const body = getEmailBody(); const body = getEmailBody();
@@ -131,6 +132,19 @@ const DubugReportModal = ({
{getLayout(viewports)} {getLayout(viewports)}
</table> </table>
</div> </div>
<div className="errors">
<h3>Errors ({errors.length})</h3>
<div className="errors-container">
{errors.map(error => {
return (
<div>
<pre>Message: {error.message}</pre>
{error.error && <pre>Stack: {error.error.stack}</pre>}
</div>
);
})}
</div>
</div>
</div> </div>
); );
}; };
@@ -1,4 +1,6 @@
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import { useLogger } from '@ohif/ui';
import { import {
save, save,
upload, upload,
@@ -95,6 +97,7 @@ export function getCommands(context, servicesManager, extensionManager) {
const { UIModalService } = servicesManager.services; const { UIModalService } = servicesManager.services;
const WrappedDebugReportModal = function() { const WrappedDebugReportModal = function() {
const { state: loggerState } = useLogger();
return ( return (
<DebugReportModal <DebugReportModal
viewports={viewports} viewports={viewports}
@@ -103,6 +106,7 @@ export function getCommands(context, servicesManager, extensionManager) {
extensionManager={extensionManager} extensionManager={extensionManager}
mailTo={state.mailTo} mailTo={state.mailTo}
debugModalMessage={state.debugModalMessage} debugModalMessage={state.debugModalMessage}
errors={loggerState.errors}
/> />
); );
}; };
+1 -1
View File
@@ -1,3 +1,3 @@
const state = { mailTo: undefined, message: undefined }; const state = { mailTo: undefined, debugModalMessage: undefined };
export default state; export default state;
+75
View File
@@ -3,6 +3,81 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.9...@ohif/extension-dicom-html@1.3.10) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [1.3.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.8...@ohif/extension-dicom-html@1.3.9) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.7...@ohif/extension-dicom-html@1.3.8) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.6...@ohif/extension-dicom-html@1.3.7) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.5...@ohif/extension-dicom-html@1.3.6) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.4...@ohif/extension-dicom-html@1.3.5) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.3...@ohif/extension-dicom-html@1.3.4) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.2...@ohif/extension-dicom-html@1.3.3) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.1...@ohif/extension-dicom-html@1.3.2) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.0...@ohif/extension-dicom-html@1.3.1) (2021-02-05) ## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.0...@ohif/extension-dicom-html@1.3.1) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-html **Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-html", "name": "@ohif/extension-dicom-html",
"version": "1.3.1", "version": "1.3.10",
"description": "OHIF extension for rendering structured reports to HTML", "description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,7 +29,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -168,6 +168,16 @@ const getContentSequence = (data, level = 1) => {
Object.keys(data).forEach(key => { Object.keys(data).forEach(key => {
const value = data[key]; const value = data[key];
if (key === '_meta') {
const HeaderDynamicLevel = `h3`;
root.push(<hr />);
root.push(
<HeaderDynamicLevel key="Metadata">
DICOM File Meta Information
</HeaderDynamicLevel>
);
}
let content; let content;
if (value instanceof Object) { if (value instanceof Object) {
content = getContentSequence(value, level + 1); content = getContentSequence(value, level + 1);
@@ -215,6 +225,7 @@ class DicomHtmlViewport extends Component {
const dicomData = DicomMessage.readFile(arrayBuffer); const dicomData = DicomMessage.readFile(arrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta); dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
dataset._meta = DicomMetaDictionary.naturalizeDataset(dataset._meta);
const mainData = getMainData(dataset); const mainData = getMainData(dataset);
const contentSequence = getContentSequence(dataset); const contentSequence = getContentSequence(dataset);
@@ -9,6 +9,7 @@ const SOP_CLASS_UIDS = {
MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50', MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50',
CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65', CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65',
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67', X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
ACQUISITION_CONTEXT_SR_STORAGE: '1.2.840.10008.5.1.4.1.1.88.71',
}; };
const sopClassUIDs = Object.values(SOP_CLASS_UIDS); const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.52.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.4...@ohif/extension-dicom-microscopy@0.52.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [0.51.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.3...@ohif/extension-dicom-microscopy@0.51.4) (2021-01-21) ## [0.51.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.3...@ohif/extension-dicom-microscopy@0.51.4) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy **Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "0.51.4", "version": "0.52.0",
"description": "OHIF extension for Dicom Microscopy", "description": "OHIF extension for Dicom Microscopy",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -70,12 +70,18 @@ class DicomMicroscopyViewport extends Component {
}); });
} catch (error) { } catch (error) {
console.error('[Microscopy Viewer] Failed to load:', error); console.error('[Microscopy Viewer] Failed to load:', error);
const { UINotificationService } = this.props.servicesManager.services; const {
UINotificationService,
LoggerService,
} = this.props.servicesManager.services;
if (UINotificationService) { if (UINotificationService) {
const message =
'Failed to load viewport. Please check that you have hardware acceleration enabled.';
LoggerService.error({ error, message });
UINotificationService.show({ UINotificationService.show({
autoClose: false,
title: 'Microscopy Viewport', title: 'Microscopy Viewport',
message: message,
'Failed to load viewport. Please check that you have hardware acceleration enabled.',
type: 'error', type: 'error',
}); });
} }
+75
View File
@@ -3,6 +3,81 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.12...@ohif/extension-dicom-rt@0.6.13) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [0.6.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.11...@ohif/extension-dicom-rt@0.6.12) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.10...@ohif/extension-dicom-rt@0.6.11) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.9...@ohif/extension-dicom-rt@0.6.10) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.8...@ohif/extension-dicom-rt@0.6.9) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.7...@ohif/extension-dicom-rt@0.6.8) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.6...@ohif/extension-dicom-rt@0.6.7) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.5...@ohif/extension-dicom-rt@0.6.6) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.4...@ohif/extension-dicom-rt@0.6.5) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.3...@ohif/extension-dicom-rt@0.6.4) (2021-02-05) ## [0.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.3...@ohif/extension-dicom-rt@0.6.4) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-rt **Note:** Version bump only for package @ohif/extension-dicom-rt
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.6.4", "version": "0.6.13",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"gl-matrix": "^3.3.0", "gl-matrix": "^3.3.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
@@ -71,8 +71,8 @@ const OHIFDicomRTStructSopClassHandler = {
} }
} }
rtStructDisplaySet.getSourceDisplaySet = function (studies) { rtStructDisplaySet.getSourceDisplaySet = function (studies, activateLabelMap = true) {
return getSourceDisplaySet(studies, rtStructDisplaySet); return getSourceDisplaySet(studies, rtStructDisplaySet, activateLabelMap);
}; };
rtStructDisplaySet.load = function (referencedDisplaySet, studies) { rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
+7 -31
View File
@@ -1,38 +1,14 @@
export default function getSourceDisplaySet(studies, rtStructDisplaySet) { import { metadata } from '@ohif/core';
const referencedDisplaySet = _getReferencedDisplaySet(
export default function getSourceDisplaySet(studies, rtStructDisplaySet, activateLabelMap = true) {
const referencedDisplaySet = metadata.StudyMetadata.getReferencedDisplaySet(
rtStructDisplaySet, rtStructDisplaySet,
studies studies
); );
rtStructDisplaySet.load(referencedDisplaySet, studies); if (activateLabelMap) {
rtStructDisplaySet.load(referencedDisplaySet, studies);
}
return referencedDisplaySet; return referencedDisplaySet;
} }
const _getReferencedDisplaySet = (rtStructDisplaySet, studies) => {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== rtStructDisplaySet.displaySetInstanceUID
);
const ReferencedSeriesSequence = Array.isArray(
rtStructDisplaySet.metadata.ReferencedSeriesSequence
)
? rtStructDisplaySet.metadata.ReferencedSeriesSequence
: [rtStructDisplaySet.metadata.ReferencedSeriesSequence];
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
return referencedDisplaySet;
};
+110
View File
@@ -3,6 +3,116 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.9...@ohif/extension-dicom-segmentation@0.6.10) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [0.6.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.8...@ohif/extension-dicom-segmentation@0.6.9) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.7...@ohif/extension-dicom-segmentation@0.6.8) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.6...@ohif/extension-dicom-segmentation@0.6.7) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.5...@ohif/extension-dicom-segmentation@0.6.6) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.4...@ohif/extension-dicom-segmentation@0.6.5) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.3...@ohif/extension-dicom-segmentation@0.6.4) (2021-04-16)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.2...@ohif/extension-dicom-segmentation@0.6.3) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.1...@ohif/extension-dicom-segmentation@0.6.2) (2021-03-31)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.0...@ohif/extension-dicom-segmentation@0.6.1) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.4...@ohif/extension-dicom-segmentation@0.6.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [0.5.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.3...@ohif/extension-dicom-segmentation@0.5.4) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.5.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.2...@ohif/extension-dicom-segmentation@0.5.3) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.1...@ohif/extension-dicom-segmentation@0.5.2) (2021-02-05) ## [0.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.1...@ohif/extension-dicom-segmentation@0.5.2) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation **Note:** Version bump only for package @ohif/extension-dicom-segmentation
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.5.2", "version": "0.6.10",
"description": "OHIF extension for viewing segmentations in the 2D MPR view", "description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -155,7 +155,9 @@ const SegmentationPanel = ({
const brushStackState = getBrushStackState(); const brushStackState = getBrushStackState();
brushStackState.activeLabelmapIndex = newLabelmapIndex; brushStackState.activeLabelmapIndex = newLabelmapIndex;
setState(state => ({ ...state, selectedSegmentation })); if (selectedSegmentation) {
setState(state => ({ ...state, selectedSegmentation }));
}
refreshViewports(); refreshViewports();
@@ -189,6 +191,10 @@ const SegmentationPanel = ({
'extensiondicomsegmentationsegloaded', 'extensiondicomsegmentationsegloaded',
refreshSegmentations refreshSegmentations
); );
document.addEventListener(
'extensiondicomsegmentationsegselected',
updateSegmentationComboBox
);
/* /*
* These are specific to each element; * These are specific to each element;
@@ -207,6 +213,10 @@ const SegmentationPanel = ({
'extensiondicomsegmentationsegloaded', 'extensiondicomsegmentationsegloaded',
refreshSegmentations refreshSegmentations
); );
document.removeEventListener(
'extensiondicomsegmentationsegselected',
updateSegmentationComboBox
);
cornerstoneTools.store.state.enabledElements.forEach(enabledElement => cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.removeEventListener( enabledElement.removeEventListener(
'cornerstonetoolslabelmapmodified', 'cornerstonetoolslabelmapmodified',
@@ -216,6 +226,27 @@ const SegmentationPanel = ({
}; };
}, [activeIndex, viewports]); }, [activeIndex, viewports]);
const updateSegmentationComboBox = (e) => {
const index = e.detail.activatedLabelmapIndex;
if (index !== -1) {
setState(state => ({ ...state, selectedSegmentation: index }));
} else {
cleanSegmentationComboBox();
}
}
const cleanSegmentationComboBox = () => {
setState(state => ({
...state,
segmentsHidden: [],
segmentNumbers: [],
labelMapList: [],
segmentList: [],
isDisabled: true,
selectedSegmentation: -1,
}));
}
const refreshSegmentations = () => { const refreshSegmentations = () => {
const activeViewport = getActiveViewport(); const activeViewport = getActiveViewport();
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID; const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
@@ -277,7 +308,10 @@ const SegmentationPanel = ({
activeViewport.SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
return referencedSegDisplaysets.map((displaySet, index) => { const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
(segDisplay => segDisplay.loadError !== true));
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
const { const {
labelmapIndex, labelmapIndex,
originLabelMapIndex, originLabelMapIndex,
@@ -289,7 +323,7 @@ const SegmentationPanel = ({
/* Map to display representation */ /* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0]; const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss'); const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const displayDate = date.format('ddd, MMM Do YYYY'); const displayDate = date.format('ddd, MMM Do YYYY, h:mm:ss a');
const displayDescription = displaySet.SeriesDescription; const displayDescription = displaySet.SeriesDescription;
return { return {
@@ -709,7 +743,7 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
referencedDisplaysets.sort((a, b) => { referencedDisplaysets.sort((a, b) => {
const aNumber = Number(`${a.SeriesDate}${a.SeriesTime}`); const aNumber = Number(`${a.SeriesDate}${a.SeriesTime}`);
const bNumber = Number(`${b.SeriesDate}${b.SeriesTime}`); const bNumber = Number(`${b.SeriesDate}${b.SeriesTime}`);
return aNumber - bNumber; return bNumber - aNumber;
}); });
return referencedDisplaysets; return referencedDisplaysets;
@@ -3,7 +3,7 @@ import Select from 'react-select';
const SegmentationSelect = ({ value, formatOptionLabel, options }) => ( const SegmentationSelect = ({ value, formatOptionLabel, options }) => (
<Select <Select
value={value} value={value === undefined ? null : value}
formatOptionLabel={formatOptionLabel} formatOptionLabel={formatOptionLabel}
styles={segmentationSelectStyles} styles={segmentationSelectStyles}
options={options} options={options}
@@ -55,6 +55,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
referencedDisplaySetUID: null, // Assigned when loaded. referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded. labelmapIndex: null, // Assigned when loaded.
isLoaded: false, isLoaded: false,
loadError: false,
hasOverlapping: false, hasOverlapping: false,
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
@@ -63,8 +64,8 @@ export default function getSopClassHandlerModule({ servicesManager }) {
metadata, metadata,
}; };
segDisplaySet.getSourceDisplaySet = function(studies) { segDisplaySet.getSourceDisplaySet = function(studies, activateLabelMap = true, onDisplaySetLoadFailureHandler) {
return getSourceDisplaySet(studies, segDisplaySet); return getSourceDisplaySet(studies, segDisplaySet, activateLabelMap, onDisplaySetLoadFailureHandler);
}; };
segDisplaySet.load = async function(referencedDisplaySet, studies) { segDisplaySet.load = async function(referencedDisplaySet, studies) {
@@ -82,57 +83,50 @@ export default function getSopClassHandlerModule({ servicesManager }) {
StudyInstanceUID, StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID referencedDisplaySet.SeriesInstanceUID
); );
return new Promise(async (resolve, reject) => {
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) {
segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error);
}
const {
labelmapBufferArray,
segMetadata,
segmentsOnFrame,
segmentsOnFrameArray,
} = results;
let labelmapIndex; const results = await _parseSeg(segArrayBuffer, imageIds);
if (labelmapBufferArray.length > 1) { if (results === undefined) {
let labelmapIndexes = []; return;
for (let i = 0; i < labelmapBufferArray.length; ++i) { }
labelmapIndexes.push( const {
await loadSegmentation( labelmapBufferArray,
imageIds, segMetadata,
segDisplaySet, segmentsOnFrame,
labelmapBufferArray[i], segmentsOnFrameArray,
segMetadata, } = results;
segmentsOnFrame, let labelmapIndex;
segmentsOnFrameArray[i] if (labelmapBufferArray.length > 1) {
) let labelmapIndexes = [];
); for (let i = 0; i < labelmapBufferArray.length; ++i) {
} labelmapIndexes.push(
/** await loadSegmentation(
* Since overlapping segmentations have virtual labelmaps, imageIds,
* originLabelMapIndex is used in the panel to select the correct dropdown value. segDisplaySet,
*/ labelmapBufferArray[i],
segDisplaySet.hasOverlapping = true; segMetadata,
segDisplaySet.originLabelMapIndex = labelmapIndexes[0]; segmentsOnFrame,
labelmapIndex = labelmapIndexes[0]; segmentsOnFrameArray[i]
console.warn('Overlapping segmentations!'); )
} else {
labelmapIndex = await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[0],
segMetadata,
segmentsOnFrame,
[]
); );
} }
resolve(labelmapIndex); /**
}); * Since overlapping segments have virtual labelmaps,
* originLabelMapIndex is used in the panel to select the correct dropdown value.
*/
segDisplaySet.hasOverlapping = true;
segDisplaySet.originLabelMapIndex = labelmapIndexes[0];
labelmapIndex = labelmapIndexes[0];
console.warn('Overlapping segments!');
} else {
labelmapIndex = await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[0],
segMetadata,
segmentsOnFrame,
[]
);
}
}; };
return segDisplaySet; return segDisplaySet;
@@ -1,88 +1,16 @@
import setActiveLabelmap from './utils/setActiveLabelMap'; import setActiveLabelmap from './utils/setActiveLabelMap';
import { classes } from '@ohif/core'; import { metadata } from '@ohif/core';
const { ImageSet } = classes; export default function getSourceDisplaySet(studies, segDisplaySet, activateLabelMap = true, onDisplaySetLoadFailureHandler) {
const referencedDisplaySet = metadata.StudyMetadata.getReferencedDisplaySet(segDisplaySet, studies);
export default function getSourceDisplaySet(studies, segDisplaySet) { let activatedLabelmapPromise;
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies); if (activateLabelMap) {
activatedLabelmapPromise = setActiveLabelmap(referencedDisplaySet, studies, segDisplaySet, undefined, onDisplaySetLoadFailureHandler);
setActiveLabelmap(referencedDisplaySet, studies, segDisplaySet);
return referencedDisplaySet;
}
const _getReferencedDisplaySet = (segDisplaySet, studies) => {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
);
const { metadata } = segDisplaySet;
let referencedSeriesInstanceUIDs;
if (metadata.ReferencedSeriesSequence) {
const ReferencedSeriesSequence = _toArray(
metadata.ReferencedSeriesSequence
);
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
} else {
const { PerFrameFunctionalGroupsSequence } = metadata;
let SourceImageSequence;
if (metadata.SourceImageSequence) {
SourceImageSequence = metadata.SourceImageSequence;
} else {
const firstFunctionalGroups = _toArray(
PerFrameFunctionalGroupsSequence
)[0];
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
}
const firstSourceImage = _toArray(SourceImageSequence)[0];
const { ReferencedSOPInstanceUID } = firstSourceImage;
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
otherDisplaySets,
ReferencedSOPInstanceUID
);
} }
const referencedDisplaySet = otherDisplaySets.find(ds => return {
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID) referencedDisplaySet : referencedDisplaySet,
); activatedLabelmapPromise : activatedLabelmapPromise
return referencedDisplaySet;
};
const _findReferencedSeriesInstanceUIDsFromSOPInstanceUID = (
displaySets,
SOPInstanceUID
) => {
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
for (let i = 0; i < imageSets.length; i++) {
const { images } = imageSets[i];
for (let j = 0; j < images.length; j++) {
if (images[j].SOPInstanceUID === SOPInstanceUID) {
return [images[j].getData().metadata.SeriesInstanceUID];
}
}
} }
};
function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
} }
+2 -1
View File
@@ -25,12 +25,13 @@ export default {
return toolbarModule; return toolbarModule;
}, },
getPanelModule({ commandsManager, api, servicesManager }) { getPanelModule({ commandsManager, api, servicesManager }) {
const { UINotificationService } = servicesManager.services; const { UINotificationService, LoggerService } = servicesManager.services;
const ExtendedSegmentationPanel = props => { const ExtendedSegmentationPanel = props => {
const { activeContexts } = api.hooks.useAppContext(); const { activeContexts } = api.hooks.useAppContext();
const onDisplaySetLoadFailureHandler = error => { const onDisplaySetLoadFailureHandler = error => {
LoggerService.error({ error, message: error.message });
UINotificationService.show({ UINotificationService.show({
title: 'DICOM Segmentation Loader', title: 'DICOM Segmentation Loader',
message: error.message, message: error.message,
@@ -20,7 +20,7 @@ export default async function setActiveLabelmap(
displaySet, displaySet,
callback = () => {}, callback = () => {},
onDisplaySetLoadFailure = err => { onDisplaySetLoadFailure = err => {
throw new Error(err.message); console.error(err.message);
} }
) { ) {
const studyMetadata = studyMetadataManager.get( const studyMetadata = studyMetadataManager.get(
@@ -48,16 +48,15 @@ export default async function setActiveLabelmap(
} }
if (!displaySet.isLoaded) { if (!displaySet.isLoaded) {
const loadPromise = displaySet.load(referencedDisplaySet, studies); try {
await displaySet.load(referencedDisplaySet, studies);
loadPromise.catch(error => { } catch (error) {
displaySet.isLoaded = false;
displaySet.loadError = true;
onDisplaySetLoadFailure(error); onDisplaySetLoadFailure(error);
// Return old index. return -1;
return activeLabelmapIndex; }
});
await loadPromise;
} }
labelmapIndex = labelmapIndex =
@@ -67,8 +66,11 @@ export default async function setActiveLabelmap(
// This might have just been created, so need to use the non-cached value. // This might have just been created, so need to use the non-cached value.
state = cornerstoneTools.getModule('segmentation').state; state = cornerstoneTools.getModule('segmentation').state;
brushStackState = state.series[firstImageId]; brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = labelmapIndex; if (brushStackState) {
brushStackState.activeLabelmapIndex = labelmapIndex;
}
refreshViewports(); refreshViewports();
callback(); callback();
+75
View File
@@ -3,6 +3,81 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.10...@ohif/extension-dicom-tag-browser@0.2.11) (2021-06-03)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.9...@ohif/extension-dicom-tag-browser@0.2.10) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [0.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.8...@ohif/extension-dicom-tag-browser@0.2.9) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.7...@ohif/extension-dicom-tag-browser@0.2.8) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.6...@ohif/extension-dicom-tag-browser@0.2.7) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.5...@ohif/extension-dicom-tag-browser@0.2.6) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.4...@ohif/extension-dicom-tag-browser@0.2.5) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.3...@ohif/extension-dicom-tag-browser@0.2.4) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.2...@ohif/extension-dicom-tag-browser@0.2.3) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.1...@ohif/extension-dicom-tag-browser@0.2.2) (2021-02-05) ## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.1...@ohif/extension-dicom-tag-browser@0.2.2) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser **Note:** Version bump only for package @ohif/extension-dicom-tag-browser
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-tag-browser", "name": "@ohif/extension-dicom-tag-browser",
"version": "0.2.2", "version": "0.2.11",
"description": "OHIF extension for checking DICOM headers.", "description": "OHIF extension for checking DICOM headers.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -28,12 +28,12 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^2.6.0", "@ohif/core": "^2.6.0",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"react": "^16.8.6" "react": "^16.8.6"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"dicomweb-client": "^0.6.0", "dicomweb-client": "^0.8.1",
"moment": "2.24.0", "moment": "2.24.0",
"react-select": "^3.0.8" "react-select": "^3.0.8"
} }
+142
View File
@@ -3,6 +3,148 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.13...@ohif/extension-vtk@1.11.14) (2021-06-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.12...@ohif/extension-vtk@1.11.13) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [1.11.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.11...@ohif/extension-vtk@1.11.12) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.10...@ohif/extension-vtk@1.11.11) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.9...@ohif/extension-vtk@1.11.10) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.8...@ohif/extension-vtk@1.11.9) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.7...@ohif/extension-vtk@1.11.8) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.6...@ohif/extension-vtk@1.11.7) (2021-04-21)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.5...@ohif/extension-vtk@1.11.6) (2021-04-16)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.4...@ohif/extension-vtk@1.11.5) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.3...@ohif/extension-vtk@1.11.4) (2021-03-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.2...@ohif/extension-vtk@1.11.3) (2021-03-25)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.1...@ohif/extension-vtk@1.11.2) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.0...@ohif/extension-vtk@1.11.1) (2021-03-09)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.11.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.6...@ohif/extension-vtk@1.11.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [1.10.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.5...@ohif/extension-vtk@1.10.6) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.4...@ohif/extension-vtk@1.10.5) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.3...@ohif/extension-vtk@1.10.4) (2021-02-05) ## [1.10.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.3...@ohif/extension-vtk@1.10.4) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.10.4", "version": "1.11.14",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -35,7 +35,7 @@
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2" "react-vtkjs-viewport": "^0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.12.3", "@ohif/core": "^2.13.13",
"@ohif/ui": "^1.8.2", "@ohif/ui": "^1.9.2",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+10 -3
View File
@@ -360,12 +360,15 @@ class OHIFVTKViewport extends Component {
} catch (error) { } catch (error) {
const errorTitle = 'Failed to load 2D MPR'; const errorTitle = 'Failed to load 2D MPR';
console.error(errorTitle, error); console.error(errorTitle, error);
const { UINotificationService } = this.props.servicesManager.services; const {
UINotificationService,
LoggerService,
} = this.props.servicesManager.services;
if (this.props.viewportIndex === 0) { if (this.props.viewportIndex === 0) {
const message = error.message.includes('buffer') const message = error.message.includes('buffer')
? 'Dataset is too big to display in MPR' ? 'Dataset is too big to display in MPR'
: error.message; : error.message;
console.error(errorTitle, error); LoggerService.error({ error, message });
UINotificationService.show({ UINotificationService.show({
title: errorTitle, title: errorTitle,
message, message,
@@ -428,11 +431,15 @@ class OHIFVTKViewport extends Component {
}; };
const onPixelDataInsertedErrorCallback = error => { const onPixelDataInsertedErrorCallback = error => {
const { UINotificationService } = this.props.servicesManager.services; const {
UINotificationService,
LoggerService,
} = this.props.servicesManager.services;
if (!this.hasError) { if (!this.hasError) {
if (this.props.viewportIndex === 0) { if (this.props.viewportIndex === 0) {
// Only show the notification from one viewport 1 in MPR2D. // Only show the notification from one viewport 1 in MPR2D.
LoggerService.error({ error, message: error.message });
UINotificationService.show({ UINotificationService.show({
title: 'MPR Load Error', title: 'MPR Load Error',
message: error.message, message: error.message,
+8 -4
View File
@@ -14,7 +14,9 @@ import OHIFVTKViewport from './OHIFVTKViewport';
const { BlendMode } = Constants; const { BlendMode } = Constants;
const commandsModule = ({ commandsManager, UINotificationService }) => { const commandsModule = ({ commandsManager, servicesManager }) => {
const { UINotificationService, LoggerService } = servicesManager.services;
// TODO: Put this somewhere else // TODO: Put this somewhere else
let apis = {}; let apis = {};
let defaultVOI; let defaultVOI;
@@ -175,7 +177,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
segmentNumber, segmentNumber,
frameIndex, frameIndex,
frame, frame,
done = () => { }, done = () => {},
}) => { }) => {
let api = apis[viewports.activeViewportIndex]; let api = apis[viewports.activeViewportIndex];
@@ -473,10 +475,12 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
const volumeLength = dimensions[0] * dimensions[1] * dimensions[2]; const volumeLength = dimensions[0] * dimensions[1] * dimensions[2];
if (volumeLength > maxBufferLengthFloat32) { if (volumeLength > maxBufferLengthFloat32) {
const message =
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data';
LoggerService.error({ message });
UINotificationService.show({ UINotificationService.show({
title: 'Browser does not support WebGL 2', title: 'Browser does not support WebGL 2',
message: message,
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data',
type: 'error', type: 'error',
autoClose: false, autoClose: false,
}); });
+1 -2
View File
@@ -35,8 +35,7 @@ const vtkExtension = {
return toolbarModule; return toolbarModule;
}, },
getCommandsModule({ commandsManager, servicesManager }) { getCommandsModule({ commandsManager, servicesManager }) {
const { UINotificationService } = servicesManager.services; return commandsModule({ commandsManager, servicesManager });
return commandsModule({ commandsManager, UINotificationService });
}, },
}; };
@@ -31,63 +31,7 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
if (!displaySet) { if (!displaySet) {
return false; return false;
} };
// 2D MPR is not currently available for 4D datasets.
// Assuming that slices at different time have the same position, here we just check if
// there are multiple slices for the same ImagePositionPatient and disable MPR.
// A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
// However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
for (let ii = 0; ii < displaySet.numImageFrames; ++ii) {
const image = displaySet.images[ii];
if (!image) continue;
const imageIdControl = image.getImageId()
const instanceMetadataControl = cornerstone.metaData.get('instance', imageIdControl)
if (!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[0];
let yImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[1];
let zImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[2];
for (let jj = ii + 1; jj < displaySet.numImageFrames; ++jj) {
const image = displaySet.images[jj];
if (!image) continue;
const imageId = image.getImageId()
const instanceMetadata = cornerstone.metaData.get('instance', imageId)
if (!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatient = instanceMetadata.ImagePositionPatient[0];
let yImagePositionPatient = instanceMetadata.ImagePositionPatient[1];
let zImagePositionPatient = instanceMetadata.ImagePositionPatient[2];
if (xImagePositionPatientControl === xImagePositionPatient &&
yImagePositionPatientControl === yImagePositionPatient &&
zImagePositionPatientControl === zImagePositionPatient) {
return false;
}
}
}
return displaySet.isReconstructable; return displaySet.isReconstructable;
}; };
+140
View File
@@ -3,6 +3,146 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.13.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.12...@ohif/core@2.13.13) (2021-06-03)
**Note:** Version bump only for package @ohif/core
## [2.13.12](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.11...@ohif/core@2.13.12) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [2.13.11](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.10...@ohif/core@2.13.11) (2021-05-17)
**Note:** Version bump only for package @ohif/core
## [2.13.10](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.9...@ohif/core@2.13.10) (2021-04-22)
**Note:** Version bump only for package @ohif/core
## [2.13.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.8...@ohif/core@2.13.9) (2021-04-22)
**Note:** Version bump only for package @ohif/core
## [2.13.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.7...@ohif/core@2.13.8) (2021-04-21)
**Note:** Version bump only for package @ohif/core
## [2.13.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.6...@ohif/core@2.13.7) (2021-04-21)
**Note:** Version bump only for package @ohif/core
## [2.13.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.5...@ohif/core@2.13.6) (2021-04-21)
**Note:** Version bump only for package @ohif/core
## [2.13.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.4...@ohif/core@2.13.5) (2021-04-16)
**Note:** Version bump only for package @ohif/core
## [2.13.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.3...@ohif/core@2.13.4) (2021-04-15)
### Bug Fixes
* **wl:** fix metadata provider window level issues after dicomweb update (string vs number) ([8681036](https://github.com/OHIF/Viewers/commit/8681036b2133cfc325658691a256a86b5e36705f))
## [2.13.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.2...@ohif/core@2.13.3) (2021-03-26)
**Note:** Version bump only for package @ohif/core
## [2.13.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.1...@ohif/core@2.13.2) (2021-03-25)
### Bug Fixes
* IDC-2288: Update toolbar tabs after derived displayset loaded ([#2307](https://github.com/OHIF/Viewers/issues/2307)) ([6e723eb](https://github.com/OHIF/Viewers/commit/6e723ebced58852007ca553792c740976cfc4201))
## [2.13.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.0...@ohif/core@2.13.1) (2021-03-09)
**Note:** Version bump only for package @ohif/core
# [2.13.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.5...@ohif/core@2.13.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [2.12.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.4...@ohif/core@2.12.5) (2021-03-03)
**Note:** Version bump only for package @ohif/core
## [2.12.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.3...@ohif/core@2.12.4) (2021-02-25)
**Note:** Version bump only for package @ohif/core
## [2.12.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.2...@ohif/core@2.12.3) (2021-02-05) ## [2.12.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.2...@ohif/core@2.12.3) (2021-02-05)
**Note:** Version bump only for package @ohif/core **Note:** Version bump only for package @ohif/core
+4 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.12.3", "version": "2.13.13",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
@@ -39,13 +39,14 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"dicomweb-client": "^0.6.0", "dicomweb-client": "^0.8.1",
"immer": "6.0.2", "immer": "6.0.2",
"isomorphic-base64": "^1.0.2", "isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.1", "lodash.merge": "^4.6.1",
"mousetrap": "^1.6.3", "mousetrap": "^1.6.3",
"retry": "^0.12.0",
"validate.js": "^0.12.0" "validate.js": "^0.12.0"
} }
} }
@@ -6,6 +6,7 @@ import parseDicomStructuredReport from './parseDicomStructuredReport';
import parseMeasurementsData from './parseMeasurementsData'; import parseMeasurementsData from './parseMeasurementsData';
import getAllDisplaySets from './utils/getAllDisplaySets'; import getAllDisplaySets from './utils/getAllDisplaySets';
import errorHandler from '../errorHandler'; import errorHandler from '../errorHandler';
import getXHRRetryRequestHook from '../utils/xhrRetryRequestHook';
const VERSION_NAME = 'dcmjs-0.0'; const VERSION_NAME = 'dcmjs-0.0';
const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1'; const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
@@ -23,6 +24,7 @@ const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
url: serverUrl, url: serverUrl,
headers: DICOMWeb.getAuthorizationHeader(), headers: DICOMWeb.getAuthorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = new api.DICOMwebClient(config);
@@ -74,6 +76,7 @@ const stowSRFromMeasurements = async (measurements, serverUrl) => {
url: serverUrl, url: serverUrl,
headers: DICOMWeb.getAuthorizationHeader(), headers: DICOMWeb.getAuthorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = new api.DICOMwebClient(config);
@@ -77,7 +77,6 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
/** /**
* Function to create imagePath with all imageData related * Function to create imagePath with all imageData related
*
* @param {string} StudyInstanceUID * @param {string} StudyInstanceUID
* @param {string} SeriesInstanceUID * @param {string} SeriesInstanceUID
* @param {string} SOPInstanceUID * @param {string} SOPInstanceUID
+7 -2
View File
@@ -63,10 +63,15 @@ export class HotkeysManager {
definitions.forEach(definition => this.registerHotkeys(definition)); definitions.forEach(definition => this.registerHotkeys(definition));
} catch (error) { } catch (error) {
const { UINotificationService } = this._servicesManager.services; const {
UINotificationService,
LoggerService,
} = this._servicesManager.services;
const message = 'Erro while setting hotkeys';
LoggerService.error({ error, message });
UINotificationService.show({ UINotificationService.show({
title: 'Hotkeys Manager', title: 'Hotkeys Manager',
message: 'Erro while setting hotkeys', message,
type: 'error', type: 'error',
}); });
} }
@@ -4,6 +4,7 @@ import dicomParser from 'dicom-parser';
import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation'; import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation';
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData'; import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData'; import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData';
import validNumber from '../utils/metadataProvider/validNumber';
class MetadataProvider { class MetadataProvider {
constructor() { constructor() {
@@ -284,7 +285,7 @@ class MetadataProvider {
break; break;
case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE: case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE:
const { WindowCenter, WindowWidth } = instance; let { WindowCenter, WindowWidth } = instance;
const windowCenter = Array.isArray(WindowCenter) const windowCenter = Array.isArray(WindowCenter)
? WindowCenter ? WindowCenter
@@ -294,15 +295,17 @@ class MetadataProvider {
: [WindowWidth]; : [WindowWidth];
metadata = { metadata = {
windowCenter, windowCenter: validNumber(windowCenter),
windowWidth, windowWidth: validNumber(windowWidth),
}; };
break; break;
case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE: case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE:
const rescaleSlope = validNumber(instance.RescaleSlope);
const rescaleIntercept = validNumber(instance.RescaleIntercept);
metadata = { metadata = {
rescaleIntercept: instance.RescaleIntercept, rescaleIntercept,
rescaleSlope: instance.RescaleSlope, rescaleSlope,
rescaleType: instance.RescaleType, rescaleType: instance.RescaleType,
}; };
break; break;
@@ -9,11 +9,12 @@ import { SeriesMetadata } from './SeriesMetadata';
import { api } from 'dicomweb-client'; import { api } from 'dicomweb-client';
// - createStacks // - createStacks
import { isImage } from '../../utils/isImage'; import { isImage } from '../../utils/isImage';
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable'; import { isDisplaySetReconstructable, isSpacingUniform } from '../../utils/isDisplaySetReconstructable';
import errorHandler from '../../errorHandler'; import errorHandler from '../../errorHandler';
import isLowPriorityModality from '../../utils/isLowPriorityModality'; import isLowPriorityModality from '../../utils/isLowPriorityModality';
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';
export class StudyMetadata extends Metadata { class StudyMetadata extends Metadata {
constructor(data, uid) { constructor(data, uid) {
super(data, uid); super(data, uid);
// Initialize Private Properties // Initialize Private Properties
@@ -226,6 +227,56 @@ export class StudyMetadata extends Metadata {
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet)); displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
} }
/**
* Returns the source display set of the derivated display set.
* @param {object} derivatedDisplaySet
* @param {array[StudyMetadata]} studies
* @return {object} source display set.
*/
static getReferencedDisplaySet(derivatedDisplaySet, studies) {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
);
const { metadata } = derivatedDisplaySet;
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence
(metadata, otherDisplaySets);
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence
(metadata);
}
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence
(metadata, otherDisplaySets);
}
const referencedSeriesAvailable = referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0;
if (referencedSeriesAvailable) {
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
;
return referencedDisplaySet;
}
};
/** /**
* Returns a list of derived datasets in the study, filtered by the given filter. * Returns a list of derived datasets in the study, filtered by the given filter.
* @param {object} filter An object containing search filters * @param {object} filter An object containing search filters
@@ -252,20 +303,7 @@ export class StudyMetadata extends Metadata {
if (referencedSeriesInstanceUID) { if (referencedSeriesInstanceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter( filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => { displaySet => {
if (!displaySet.metadata.ReferencedSeriesSequence) { return StudyMetadata.getReferencedDisplaySet(displaySet, [this]).SeriesInstanceUID === referencedSeriesInstanceUID;
return false;
}
const ReferencedSeriesSequence = Array.isArray(
displaySet.metadata.ReferencedSeriesSequence
)
? displaySet.metadata.ReferencedSeriesSequence
: [displaySet.metadata.ReferencedSeriesSequence];
return ReferencedSeriesSequence.some(
ReferencedSeries =>
ReferencedSeries.SeriesInstanceUID === referencedSeriesInstanceUID
);
} }
); );
} }
@@ -273,7 +311,7 @@ export class StudyMetadata extends Metadata {
if (referencedFrameOfReferenceUID) { if (referencedFrameOfReferenceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter( filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => displaySet =>
displaySet.ReferencedFrameOfReferenceUID === displaySet.FrameOfReferenceUID ===
referencedFrameOfReferenceUID referencedFrameOfReferenceUID
); );
} }
@@ -767,6 +805,16 @@ const isMultiFrame = instance => {
return instance.getTagValue('NumberOfFrames') > 1; return instance.getTagValue('NumberOfFrames') > 1;
}; };
/**
* Creates a display set for a series.
* Checks if a series is reconstructable to a 3D volume.
* If reconstructable, the frames are sorted.
*
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} imageSet.
*/
const makeDisplaySet = (series, instances) => { const makeDisplaySet = (series, instances) => {
const instance = instances[0]; const instance = instances[0];
const imageSet = new ImageSet(instances); const imageSet = new ImageSet(instances);
@@ -786,7 +834,7 @@ const makeDisplaySet = (series, instances) => {
isMultiFrame: isMultiFrame(instance), isMultiFrame: isMultiFrame(instance),
}); });
// Sort the images in this series if needed // Sort the images in this series by instanceNumber
const shallSort = true; //!OHIF.utils.ObjectPath.get(Meteor, 'settings.public.ui.sortSeriesByIncomingOrder'); const shallSort = true; //!OHIF.utils.ObjectPath.get(Meteor, 'settings.public.ui.sortSeriesByIncomingOrder');
if (shallSort) { if (shallSort) {
imageSet.sortBy((a, b) => { imageSet.sortBy((a, b) => {
@@ -804,18 +852,32 @@ const makeDisplaySet = (series, instances) => {
imageSet.getImage(0).getTagValue('InstanceNumber') imageSet.getImage(0).getTagValue('InstanceNumber')
); );
const isReconstructable = isDisplaySetReconstructable(instances); const displayReconstructableInfo = isDisplaySetReconstructable(instances);
imageSet.isReconstructable = displayReconstructableInfo.value;
imageSet.isReconstructable = isReconstructable.value;
let displaySpacingInfo = undefined;
if (shallSort && imageSet.isReconstructable) { if (shallSort && imageSet.isReconstructable) {
// sort images by image position
imageSet.sortByImagePositionPatient(); imageSet.sortByImagePositionPatient();
// check if the spacing is uniform and update isReconstructable
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find
(issue => issue === ReconstructionIssues.DATASET_4D);
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
imageSet.isReconstructable = displaySpacingInfo.isUniform;
if (displaySpacingInfo.missingFrames) {
// TODO -> This is currently unused, but may be used for reconstructing
// Volumes with gaps later on.
imageSet.missingFrames = displaySpacingInfo.missingFrames;
}
} }
if (isReconstructable.missingFrames) { if (!imageSet.displayReconstructableInfo) {
// TODO -> This is currently unused, but may be used for reconstructing // It is not reconstrabale Save type of warning
// Volumes with gaps later on. imageSet.reconstructionIssues = displaySpacingInfo ?
imageSet.missingFrames = isReconstructable.missingFrames; displayReconstructableInfo.reconstructionIssues.concat(displaySpacingInfo.reconstructionIssues) :
displayReconstructableInfo.reconstructionIssues;
} }
return imageSet; return imageSet;
@@ -878,6 +940,7 @@ function _getDisplaySetFromSopClassModule(
url: study.getData().wadoRoot, url: study.getData().wadoRoot,
headers, headers,
errorInterceptor, errorInterceptor,
requestHooks: [getXHRRetryRequestHook()],
}); });
let displaySet = plugin.getDisplaySetFromSeries( let displaySet = plugin.getDisplaySetFromSeries(
@@ -892,3 +955,147 @@ function _getDisplaySetFromSopClassModule(
} }
return displaySet; return displaySet;
} }
/**
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedSeriesSequence.
* @param {object} derivatedDisplaySet.metadata
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence (
metadata,
) {
if (!metadata.ReferencedSeriesSequence) {
return;
}
let referencedSeriesInstanceUIDs;
const ReferencedSeriesSequence = _toArray(
metadata.ReferencedSeriesSequence
);
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
return referencedSeriesInstanceUIDs;
};
/**
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedImageSequence.
* @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
metadata,
displaySets
) {
if (!metadata.ReferencedImageSequence) {
return;
}
let referencedSeriesInstanceUIDs;
const referencedImageArray = _toArray(metadata.ReferencedImageSequence);
for (let i = 0; i < referencedImageArray.length; i++) {
const { ReferencedSOPInstanceUID } = referencedImageArray[i];
if (!ReferencedSOPInstanceUID) {
continue;
}
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
displaySets,
ReferencedSOPInstanceUID
);
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
break;
}
}
return referencedSeriesInstanceUIDs;
};
/**
* Returns the referenced series instance UIDs by searching the information in the
* SourceImageSequence.
* @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
metadata,
displaySets
) {
let SourceImageSequence;
if (metadata.SourceImageSequence) {
SourceImageSequence = metadata.SourceImageSequence;
} else {
const { PerFrameFunctionalGroupsSequence } = metadata;
const firstFunctionalGroups = _toArray(
PerFrameFunctionalGroupsSequence
)[0];
if (firstFunctionalGroups) {
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
}
}
if (!SourceImageSequence) {
return;
}
const sourceImageArray = _toArray(SourceImageSequence);
let referencedSeriesInstanceUIDs;
for (let i = 0; i < sourceImageArray.length; i++) {
const { ReferencedSOPInstanceUID } = sourceImageArray[i];
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
displaySets,
ReferencedSOPInstanceUID
);
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
break;
}
}
return referencedSeriesInstanceUIDs;
};
/**
* Returns the referenced series instance UIDs by searching the information in the
* SOPInstanceUID of the displaySets.
* @param {array[object]} displaysets
* @param {string} SOPInstanceUID
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
displaySets,
SOPInstanceUID
) {
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
for (let i = 0; i < imageSets.length; i++) {
const { images } = imageSets[i];
if (!images) {
continue;
}
for (let j = 0; j < images.length; j++) {
const image = images[j];
if (!image) {
continue;
}
if (image.SOPInstanceUID === SOPInstanceUID) {
return [image.getData().metadata.SeriesInstanceUID];
}
}
}
};
function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
}
export {StudyMetadata};
+11
View File
@@ -0,0 +1,11 @@
const ReconstructionIssues = {
DATASET_4D: 'datasetis4D',
VARYING_IMAGESDIMENSIONS: 'imagesdimensionsvarying',
VARYING_IMAGESCOMPONENTS: 'imagescomponentsvarying',
VARYING_IMAGESORIENTATION: 'imagesorientationvarying',
MISSING_FRAMES: 'missingframes',
IRREGULAR_SPACING: 'irregularspacing',
MULTIFFRAMES: 'multiframe',
};
export {ReconstructionIssues};
+3
View File
@@ -26,6 +26,7 @@ import {
UIModalService, UIModalService,
UIDialogService, UIDialogService,
MeasurementService, MeasurementService,
LoggerService,
} from './services'; } from './services';
const OHIF = { const OHIF = {
@@ -60,6 +61,7 @@ const OHIF = {
UIModalService, UIModalService,
UIDialogService, UIDialogService,
MeasurementService, MeasurementService,
LoggerService,
}; };
export { export {
@@ -93,6 +95,7 @@ export {
UIModalService, UIModalService,
UIDialogService, UIDialogService,
MeasurementService, MeasurementService,
LoggerService,
}; };
export { OHIF }; export { OHIF };
+1
View File
@@ -14,6 +14,7 @@ describe('Top level exports', () => {
'UIModalService', 'UIModalService',
'UIDialogService', 'UIDialogService',
'MeasurementService', 'MeasurementService',
'LoggerService',
// //
'utils', 'utils',
'hotkeys', 'hotkeys',
@@ -0,0 +1,67 @@
const name = 'LoggerService';
const publicAPI = {
name,
info: _info,
error: _error,
setServiceImplementation,
};
const serviceImplementation = {
_info: () => console.warn('info() NOT IMPLEMENTED'),
_error: () => console.warn('error() NOT IMPLEMENTED'),
};
/**
* Logs an info
*
* @param {object} props { message, displayOnConsole }
*/
function _info({ message, displayOnConsole }) {
return serviceImplementation._info({
message,
displayOnConsole,
});
}
/**
* Logs an error
*
* @param {object} props { error, stack, message, displayOnConsole }
* @returns void
*/
function _error({ error, stack, message, displayOnConsole }) {
return serviceImplementation._error({
error,
stack,
message,
displayOnConsole,
});
}
/**
*
*
* @param {*} {
* info: infoImplementation,
* error: errorImplementation,
* }
*/
function setServiceImplementation({
info: infoImplementation,
error: errorImplementation,
}) {
if (infoImplementation) {
serviceImplementation._info = infoImplementation;
}
if (errorImplementation) {
serviceImplementation._error = errorImplementation;
}
}
export default {
name,
create: ({ configuration = {} }) => {
return publicAPI;
},
};
+2
View File
@@ -3,6 +3,7 @@ import UINotificationService from './UINotificationService';
import UIModalService from './UIModalService'; import UIModalService from './UIModalService';
import UIDialogService from './UIDialogService'; import UIDialogService from './UIDialogService';
import MeasurementService from './MeasurementService'; import MeasurementService from './MeasurementService';
import LoggerService from './LoggerService';
export { export {
UINotificationService, UINotificationService,
@@ -10,4 +11,5 @@ export {
UIDialogService, UIDialogService,
ServicesManager, ServicesManager,
MeasurementService, MeasurementService,
LoggerService,
}; };
@@ -2,6 +2,7 @@ import { api } from 'dicomweb-client';
import DICOMWeb from '../../../DICOMWeb/'; import DICOMWeb from '../../../DICOMWeb/';
import errorHandler from '../../../errorHandler'; import errorHandler from '../../../errorHandler';
import getXHRRetryRequestHook from '../../../utils/xhrRetryRequestHook';
/** /**
* Creates a QIDO date string for a date range query * Creates a QIDO date string for a date range query
@@ -118,6 +119,7 @@ export default function Studies(server, filter) {
url: server.qidoRoot, url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = new api.DICOMwebClient(config);
@@ -10,6 +10,7 @@ import {
} from './studyInstanceHelpers'; } from './studyInstanceHelpers';
import errorHandler from '../../../errorHandler'; import errorHandler from '../../../errorHandler';
import { getXHRRetryRequestHook } from '../../../utils/xhrRetryRequestHook';
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary; const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
@@ -76,6 +77,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
url: server.qidoRoot, url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}); });
this.client = client; this.client = client;
@@ -4,6 +4,7 @@ import { createStudyFromSOPInstanceList } from './studyInstanceHelpers';
import RetrieveMetadataLoader from './retrieveMetadataLoader'; import RetrieveMetadataLoader from './retrieveMetadataLoader';
import errorHandler from '../../../errorHandler'; import errorHandler from '../../../errorHandler';
import getXHRRetryRequestHook from '../../../utils/xhrRetryRequestHook';
/** /**
* Class for sync load of study metadata. * Class for sync load of study metadata.
@@ -61,6 +62,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader {
url: server.wadoRoot, url: server.wadoRoot,
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}); });
this.client = client; this.client = client;
@@ -4,6 +4,7 @@ import { api } from 'dicomweb-client';
import DICOMWeb from '../DICOMWeb'; import DICOMWeb from '../DICOMWeb';
import errorHandler from '../errorHandler'; import errorHandler from '../errorHandler';
import getXHRRetryRequestHook from './xhrRetryRequestHook';
const getImageId = imageObj => { const getImageId = imageObj => {
if (!imageObj) { if (!imageObj) {
@@ -66,6 +67,7 @@ const wadorsRetriever = (
url, url,
headers, headers,
errorInterceptor, errorInterceptor,
requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = new api.DICOMwebClient(config);
+3
View File
@@ -18,6 +18,7 @@ import isDicomUid from './isDicomUid';
import resolveObjectPath from './resolveObjectPath'; import resolveObjectPath from './resolveObjectPath';
import * as hierarchicalListUtils from './hierarchicalListUtils'; import * as hierarchicalListUtils from './hierarchicalListUtils';
import * as progressTrackingUtils from './progressTrackingUtils'; import * as progressTrackingUtils from './progressTrackingUtils';
import xhrRetryRequestHook from './xhrRetryRequestHook';
const utils = { const utils = {
guid, guid,
@@ -40,6 +41,7 @@ const utils = {
resolveObjectPath, resolveObjectPath,
hierarchicalListUtils, hierarchicalListUtils,
progressTrackingUtils, progressTrackingUtils,
xhrRetryRequestHook,
}; };
export { export {
@@ -63,6 +65,7 @@ export {
resolveObjectPath, resolveObjectPath,
hierarchicalListUtils, hierarchicalListUtils,
progressTrackingUtils, progressTrackingUtils,
xhrRetryRequestHook,
}; };
export default utils; export default utils;
+1
View File
@@ -23,6 +23,7 @@ describe('Top level exports', () => {
'resolveObjectPath', 'resolveObjectPath',
'hierarchicalListUtils', 'hierarchicalListUtils',
'progressTrackingUtils', 'progressTrackingUtils',
'xhrRetryRequestHook',
].sort(); ].sort();
const exports = Object.keys(utils.default).sort(); const exports = Object.keys(utils.default).sort();
@@ -1,9 +1,14 @@
import { ReconstructionIssues } from './../enums.js';
/** /**
* Checks if a series is reconstructable to a 3D volume. * Checks if a series is reconstructable to a 3D volume.
* *
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects. * @param {Object[]} An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} value, reconstructionIssues.
*/ */
export default function isDisplaySetReconstructable(instances) { function isDisplaySetReconstructable(instances) {
if (!instances.length) { if (!instances.length) {
return { value: false }; return { value: false };
} }
@@ -23,31 +28,45 @@ export default function isDisplaySetReconstructable(instances) {
} }
if (isMultiframe) { if (isMultiframe) {
return processMultiframe(instances[0]); return processMultiframe();
} else { } else {
return processSingleframe(instances); return processSingleframe(instances);
} }
} }
function processMultiframe(instance) { /**
//TODO: deal with multriframe checks! return false for now as can't reconstruct. * Process reconstructable multiframes checks
return { value: false }; * TODO: deal with multriframe checks! return false for now as can't reconstruct.
* *
* @returns {Object} value and reconstructionIssues.
*/
function processMultiframe() {
const reconstructionIssues = [ReconstructionIssues.MULTIFRAMES];
return { value: false, reconstructionIssues };
} }
/**
* Process reconstructable single frame checks
*
* @param {Object[]} An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} value and reconstructionIssues.
*/
function processSingleframe(instances) { function processSingleframe(instances) {
const n = instances.length;
const firstImage = instances[0].getData().metadata; const firstImage = instances[0].getData().metadata;
const firstImageRows = firstImage.Rows; const firstImageRows = firstImage.Rows;
const firstImageColumns = firstImage.Columns; const firstImageColumns = firstImage.Columns;
const firstImageSamplesPerPixel = firstImage.SamplesPerPixel; const firstImageSamplesPerPixel = firstImage.SamplesPerPixel;
const firstImageOrientationPatient = firstImage.ImageOrientationPatient; const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
const firstImagePositionPatient = firstImage.ImagePositionPatient;
const reconstructionIssues = [];
// Can't reconstruct if we: // Can't reconstruct if we:
// -- Have a different dimensions within a displaySet. // -- Have a different dimensions within a displaySet.
// -- Have a different number of components within a displaySet. // -- Have a different number of components within a displaySet.
// -- Have different orientations within a displaySet. // -- Have different orientations within a displaySet.
for (let i = 1; i < instances.length; i++) { for (let ii = 1; ii < n; ++ii) {
const instance = instances[i].getData().metadata; const instance = instances[ii].getData().metadata;
const { const {
Rows, Rows,
Columns, Columns,
@@ -55,67 +74,146 @@ function processSingleframe(instances) {
ImageOrientationPatient, ImageOrientationPatient,
} = instance; } = instance;
if ( if (Rows !== firstImageRows || Columns !== firstImageColumns) {
Rows !== firstImageRows || reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
Columns !== firstImageColumns || } else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
SamplesPerPixel !== firstImageSamplesPerPixel || reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
!_isSameOrientation(ImageOrientationPatient, firstImageOrientationPatient) } else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
) { reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
return { value: false }; }
if (reconstructionIssues.length !== 0) {
break;
} }
} }
// check if dataset is 4D
if (_isDataset4D(instances)) {
reconstructionIssues.push(ReconstructionIssues.DATASET_4D);
}
return { value: reconstructionIssues.length === 0 ? true : false, reconstructionIssues };
}
/**
* Check is the spacing is uniform.
* The input metadata array has to be ordered by image position.
*
* @param {Object[]} An array of `OHIFInstanceMetadata` objects.
* @param {boolean} is the dataset 4D.
*
* @returns {Object} isUniform, reconstructionIssues and missingFrames
*/
function isSpacingUniform(instances, datasetIs4D) {
const n = instances.length;
const firstImage = instances[0].getData().metadata;
const firstImagePositionPatient = firstImage.ImagePositionPatient;
const reconstructionIssues = [];
let missingFrames = 0; let missingFrames = 0;
// Check if frame spacing is approximately equal within a spacingTolerance. // Check if frame spacing is approximately equal within a spacingTolerance.
// If spacing is on a uniform grid but we are missing frames, // If spacing is on a uniform grid but we are missing frames,
// Allow reconstruction, but pass back the number of missing frames. // Allow reconstruction, but pass back the number of missing frames.
if (instances.length > 2) { if (n > 2) {
const lastIpp = instances[instances.length - 1].getData().metadata const lastIpp = instances[n - 1].getData().metadata
.ImagePositionPatient; .ImagePositionPatient;
// We can't reconstruct if we are missing ImagePositionPatient values // We can't reconstruct if we are missing ImagePositionPatient values
if (!firstImagePositionPatient || !lastIpp) { if (firstImagePositionPatient && lastIpp) {
return { value: false }; const averageSpacingBetweenFrames =
} _getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
(n - 1);
const averageSpacingBetweenFrames = let previousImagePositionPatient = firstImagePositionPatient;
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
(instances.length - 1);
let previousImagePositionPatient = firstImagePositionPatient; for (let ii = 1; ii < n; ++ii) {
const instance = instances[ii].getData().metadata;
const { ImagePositionPatient } = instance;
for (let i = 1; i < instances.length; i++) { const spacingBetweenFrames = _getPerpendicularDistance(
const instance = instances[i].getData().metadata; ImagePositionPatient,
const { ImagePositionPatient } = instance; previousImagePositionPatient
);
const spacingBetweenFrames = _getPerpendicularDistance( if (datasetIs4D && spacingBetweenFrames < 1.e-3) {
ImagePositionPatient, // the dataset is 4D, if the distance is zero, means that we are
previousImagePositionPatient // checking the 4th dimension. Do not return, since we want still to
); // check the 3rd dimension spacing.
const spacingIssue = _getSpacingIssue( continue;
spacingBetweenFrames,
averageSpacingBetweenFrames
);
if (spacingIssue) {
const issue = spacingIssue.issue;
if (issue === reconstructionIssues.MISSING_FRAMES) {
missingFrames += spacingIssue.missingFrames;
} else if (issue === reconstructionIssues.IRREGULAR_SPACING) {
return { value: false };
} }
}
previousImagePositionPatient = ImagePositionPatient; const spacingIssue = _getSpacingIssue(
spacingBetweenFrames,
averageSpacingBetweenFrames
);
if (spacingIssue) {
const issue = spacingIssue.issue;
if (issue === ReconstructionIssues.MISSING_FRAMES) {
missingFrames += spacingIssue.missingFrames;
} else if (issue === ReconstructionIssues.IRREGULAR_SPACING) {
reconstructionIssues.push(issue);
break;
}
}
previousImagePositionPatient = ImagePositionPatient;
}
} }
} }
return { value: true, missingFrames }; return { isUniform: reconstructionIssues.length === 0 ? true : false, missingFrames, reconstructionIssues };
} }
function _isSameOrientation(iop1, iop2) {
/**
* Check if 4D dataset.
*
* Assuming that slices at different time have the same position, here we just check if
* there are multiple slices for the same ImagePositionPatient and disable MPR.
*
* A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
* However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
*
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
*
* @returns {boolean} dataset4D value.
*/
function _isDataset4D(instances) {
const n = instances.length;
for (let ii = 0; ii < n; ++ii) {
const instanceMetadataControl = instances[ii].getData().metadata;
if (
!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined
) {
continue;
}
for (let jj = ii + 1; jj < n; ++jj) {
const instanceMetadata = instances[jj].getData().metadata;
if (
!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined
) {
continue;
}
if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) {
return true;
}
}
}
return false;
}
function _isSameArray(iop1, iop2) {
if (iop1 === undefined || !iop2 === undefined) { if (iop1 === undefined || !iop2 === undefined) {
return; return;
} }
@@ -156,12 +254,12 @@ function _getSpacingIssue(spacing, averageSpacing) {
if (errorForEachSpacing < spacingTolerance * averageSpacing) { if (errorForEachSpacing < spacingTolerance * averageSpacing) {
return { return {
issue: reconstructionIssues.MISSING_FRAMES, issue: ReconstructionIssues.MISSING_FRAMES,
missingFrames: numberOfSpacings - 1, missingFrames: numberOfSpacings - 1,
}; };
} }
return { issue: reconstructionIssues.IRREGULAR_SPACING }; return { issue: ReconstructionIssues.IRREGULAR_SPACING };
} }
function _getPerpendicularDistance(a, b) { function _getPerpendicularDistance(a, b) {
@@ -173,7 +271,5 @@ function _getPerpendicularDistance(a, b) {
} }
const constructableModalities = ['MR', 'CT', 'PT', 'NM']; const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
const reconstructionIssues = {
MISSING_FRAMES: 'missingframes', export {isDisplaySetReconstructable, isSpacingUniform};
IRREGULAR_SPACING: 'irregularspacing',
};
@@ -45,13 +45,13 @@ import studyMetadataManager from './studyMetadataManager';
* @param {string} referencedDisplaySet.studyInstanceUid * @param {string} referencedDisplaySet.studyInstanceUid
* @param {Array} referencedDisplaySet.sopClassUids * @param {Array} referencedDisplaySet.sopClassUids
* @param {Study[]} studies Collection of studies * @param {Study[]} studies Collection of studies
* @param {object} logger
* @param {object} snackbar
* @returns void * @returns void
*/ */
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => { async function loadAndCacheDerivedDisplaySets(referencedDisplaySet, studies, logger, snackbar) {
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet; const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
const promises = []; const promises = [];
const studyMetadata = studyMetadataManager.get(StudyInstanceUID); const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
if (!studyMetadata) { if (!studyMetadata) {
@@ -80,11 +80,10 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
}); });
// For each type, see if any are loaded, if not load the most recent. // For each type, see if any are loaded, if not load the most recent.
Object.keys(displaySetsPerModality).forEach(key => { await Promise.all(Object.keys(displaySetsPerModality).map(async (key) => {
const displaySets = displaySetsPerModality[key]; const displaySets = displaySetsPerModality[key];
const isLoaded = displaySets.some(displaySet => displaySet.isLoaded); const isLoaded = displaySets.some(displaySet => displaySet.isLoaded);
if (isLoaded) { if (isLoaded) {
return; return;
} }
@@ -107,12 +106,34 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
} }
}); });
recentDisplaySet.isLoading = true; try {
if (recentDisplaySet.hasOwnProperty('getSourceDisplaySet') &&
typeof recentDisplaySet.getSourceDisplaySet === 'function') {
await recentDisplaySet.getSourceDisplaySet(studies);
} else {
await recentDisplaySet.load(referencedDisplaySet, studies);
}
} catch (error) {
recentDisplaySet.isLoaded = false;
recentDisplaySet.loadError = true;
logger.error({ error, message: error.message });
snackbar.show({
title: 'Error loading derived display set:',
message: error.message,
type: 'error',
error,
autoClose: false,
});
}
}));
promises.push(recentDisplaySet.load(referencedDisplaySet, studies)); /*
}); * TODO: Improve the way we notify parts of the app
* that depends on derived display sets to be loaded.
return promises; * (Implement pubsub for better tracking of derived display sets)
*/
const event = new CustomEvent('deriveddisplaysetsloadedandcached');
document.dispatchEvent(event);
}; };
export default loadAndCacheDerivedDisplaySets; export default loadAndCacheDerivedDisplaySets;
@@ -4,6 +4,7 @@ import str2ab from '../str2ab';
import unpackOverlay from './unpackOverlay'; import unpackOverlay from './unpackOverlay';
import errorHandler from '../../errorHandler'; import errorHandler from '../../errorHandler';
import getXHRRetryRequestHook from '../xhrRetryRequestHook';
export default async function fetchOverlayData(instance, server) { export default async function fetchOverlayData(instance, server) {
const OverlayDataPromises = []; const OverlayDataPromises = [];
@@ -69,6 +70,7 @@ async function _getOverlayData(tag, server) {
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = new api.DICOMwebClient(config);
const options = { const options = {
@@ -3,6 +3,7 @@ import DICOMWeb from '../../DICOMWeb';
import str2ab from '../str2ab'; import str2ab from '../str2ab';
import errorHandler from '../../errorHandler'; import errorHandler from '../../errorHandler';
import getXHRRetryRequestHook from '../xhrRetryRequestHook';
export default async function fetchPaletteColorLookupTableData( export default async function fetchPaletteColorLookupTableData(
instance, instance,
@@ -141,6 +142,7 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = new api.DICOMwebClient(config);
const options = { const options = {
@@ -0,0 +1,9 @@
const validNumber = val => {
if (Array.isArray(val)) {
return val.map(v => (v !== undefined ? Number(v) : v));
} else {
return val !== undefined ? Number(val) : val;
}
};
export default validNumber;
@@ -0,0 +1,103 @@
import retry from 'retry';
const defaultRetryOptions = {
retries: 5,
factor: 3,
minTimeout: 1 * 1000,
maxTimeout: 60 * 1000,
randomize: true,
retryableStatusCodes: [429, 500],
};
let retryOptions = { ...defaultRetryOptions };
/**
* Request hook used to add retry functionality to XHR requests.
*
* @param {XMLHttpRequest} request XHR request instance
* @param {object} metadata Metadata about the request
* @param {object} metadata.url URL
* @param {object} metadata.method HTTP method
* @returns {XMLHttpRequest} request instance optionally modified
*/
const xhrRetryRequestHook = (request, metadata) => {
const { url, method } = metadata;
function faultTolerantRequestSend(...args) {
const operation = retry.operation(retryOptions);
operation.attempt(function operationAttempt(currentAttempt) {
const originalOnReadyStateChange = request.onreadystatechange;
/** Overriding/extending XHR function */
request.onreadystatechange = function onReadyStateChange(...args) {
originalOnReadyStateChange.apply(request, args);
if (retryOptions.retryableStatusCodes.includes(request.status)) {
const errorMessage = `Attempt to request ${url} failed.`;
const attemptFailedError = new Error(errorMessage);
operation.retry(attemptFailedError);
}
};
/** Call open only on retry (after headers and other things were set in the xhr instance) */
if (currentAttempt > 1) {
console.warn(`Requesting ${url}... (attempt: ${currentAttempt})`);
request.open(method, url, true);
}
});
originalRequestSend.apply(request, args);
}
/** Overriding/extending XHR function */
const originalRequestSend = request.send;
request.send = faultTolerantRequestSend;
return request;
};
/**
* Returns a configured retry request hook function
* that can be used to add retry functionality to XHR request.
*
* Default options:
* retries: 5
* factor: 3
* minTimeout: 1 * 1000
* maxTimeout: 60 * 1000
* randomize: true
*
* @param {object} options
* @param {number} options.retires number of retries
* @param {number} options.factor factor
* @param {number} options.minTimeout the min timeout
* @param {number} options.maxTimeout the max timeout
* @param {boolean} options.randomize randomize
* @param {array} options.retryableStatusCodes status codes that can trigger retry
* @returns {function} the configured retry request function
*/
export const getXHRRetryRequestHook = (options = {}) => {
retryOptions = { ...defaultRetryOptions };
if ('retries' in options) {
retryOptions.retries = options.retries;
}
if ('factor' in options) {
retryOptions.factor = options.factor;
}
if ('minTimeout' in options) {
retryOptions.minTimeout = options.minTimeout;
}
if ('maxTimeout' in options) {
retryOptions.maxTimeout = options.maxTimeout;
}
if ('randomize' in options) {
retryOptions.randomize = options.randomize;
}
if ('retryableStatusCodes' in options) {
retryOptions.retryableStatusCodes = options.retryableStatusCodes;
}
return xhrRetryRequestHook;
};
export default getXHRRetryRequestHook;
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.1...@ohif/ui@1.9.2) (2021-05-17)
**Note:** Version bump only for package @ohif/ui
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.0...@ohif/ui@1.9.1) (2021-03-26)
**Note:** Version bump only for package @ohif/ui
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.2...@ohif/ui@1.9.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [1.8.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.1...@ohif/ui@1.8.2) (2021-02-05) ## [1.8.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.1...@ohif/ui@1.8.2) (2021-02-05)
**Note:** Version bump only for package @ohif/ui **Note:** Version bump only for package @ohif/ui
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.8.2", "version": "1.9.2",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -3,6 +3,7 @@ import './ImageThumbnail.styl';
import { utils } from '@ohif/core'; import { utils } from '@ohif/core';
import React, { useState, useEffect, createRef } from 'react'; import React, { useState, useEffect, createRef } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator'; import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
@@ -15,6 +16,7 @@ import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
//import cornerstone from 'cornerstone-core'; //import cornerstone from 'cornerstone-core';
function ImageThumbnail(props) { function ImageThumbnail(props) {
const { const {
active,
width, width,
height, height,
imageSrc, imageSrc,
@@ -97,7 +99,7 @@ function ImageThumbnail(props) {
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]); }, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
return ( return (
<div className="ImageThumbnail"> <div className={classNames('ImageThumbnail', { active: active })}>
<div className="image-thumbnail-canvas"> <div className="image-thumbnail-canvas">
{shouldRenderToCanvas() ? ( {shouldRenderToCanvas() ? (
<canvas ref={canvasRef} width={width} height={height} /> <canvas ref={canvasRef} width={width} height={height} />
@@ -126,6 +128,7 @@ function ImageThumbnail(props) {
} }
ImageThumbnail.propTypes = { ImageThumbnail.propTypes = {
active: PropTypes.bool,
imageSrc: PropTypes.string, imageSrc: PropTypes.string,
imageId: PropTypes.string, imageId: PropTypes.string,
error: PropTypes.bool, error: PropTypes.bool,
@@ -135,6 +138,7 @@ ImageThumbnail.propTypes = {
}; };
ImageThumbnail.defaultProps = { ImageThumbnail.defaultProps = {
active: false,
error: false, error: false,
stackPercentComplete: 0, stackPercentComplete: 0,
width: 217, width: 217,
@@ -6,7 +6,7 @@
--sidebar-transition: all 0.3s ease; --sidebar-transition: all 0.3s ease;
} }
.ThumbnailEntry.active .ImageThumbnail .ImageThumbnail.active
border-color: var(--active-color); border-color: var(--active-color);
box-shadow: none box-shadow: none
transition: var(--sidebar-transition); transition: var(--sidebar-transition);
@@ -14,7 +14,7 @@
.ImageThumbnail .ImageThumbnail
background-color: var(--primary-background-color); background-color: var(--primary-background-color);
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark); box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
border: 5px solid transparent border: 2px solid transparent
border-radius: 12px border-radius: 12px
height: 135px height: 135px
margin: 0 auto margin: 0 auto
@@ -20,6 +20,7 @@ function StudyBrowser(props) {
return study.thumbnails.map((thumb, thumbIndex) => { return study.thumbnails.map((thumb, thumbIndex) => {
// TODO: Thumb has more props than we care about? // TODO: Thumb has more props than we care about?
const { const {
active,
altImageText, altImageText,
displaySetInstanceUID, displaySetInstanceUID,
imageId, imageId,
@@ -28,6 +29,7 @@ function StudyBrowser(props) {
SeriesDescription, SeriesDescription,
SeriesNumber, SeriesNumber,
stackPercentComplete, stackPercentComplete,
hasWarnings,
} = thumb; } = thumb;
return ( return (
@@ -37,6 +39,7 @@ function StudyBrowser(props) {
data-cy="thumbnail-list" data-cy="thumbnail-list"
> >
<Thumbnail <Thumbnail
active={active}
supportsDrag={supportsDrag} supportsDrag={supportsDrag}
key={`${studyIndex}_${thumbIndex}`} key={`${studyIndex}_${thumbIndex}`}
id={`${studyIndex}_${thumbIndex}`} // Unused? id={`${studyIndex}_${thumbIndex}`} // Unused?
@@ -50,6 +53,7 @@ function StudyBrowser(props) {
numImageFrames={numImageFrames} numImageFrames={numImageFrames}
SeriesDescription={SeriesDescription} SeriesDescription={SeriesDescription}
SeriesNumber={SeriesNumber} SeriesNumber={SeriesNumber}
hasWarnings={hasWarnings}
stackPercentComplete={stackPercentComplete} stackPercentComplete={stackPercentComplete}
// Events // Events
onClick={onThumbnailClick.bind( onClick={onThumbnailClick.bind(
@@ -1,8 +1,11 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd'; import { useDrag } from 'react-dnd';
import ImageThumbnail from './ImageThumbnail'; import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger';
import './Thumbnail.styl'; import './Thumbnail.styl';
@@ -11,7 +14,22 @@ function ThumbnailFooter({
SeriesNumber, SeriesNumber,
InstanceNumber, InstanceNumber,
numImageFrames, numImageFrames,
hasWarnings
}) { }) {
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
useEffect(() => {
let unmounted = false
hasWarnings.then(response => {
if (!unmounted) {
inconsistencyWarningsSet(response)
}
})
return () => {
unmounted = true
}
}, [])
const infoOnly = !SeriesDescription; const infoOnly = !SeriesDescription;
const getInfo = (value, icon, className = '') => { const getInfo = (value, icon, className = '') => {
@@ -22,28 +40,73 @@ function ThumbnailFooter({
</div> </div>
); );
}; };
const getWarningContent = (inconsistencyWarnings) => {
if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{inconsistencyWarnings}</React.Fragment>;
}
};
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
return(
<React.Fragment>
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
<OverlayTrigger
key={SeriesNumber}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
<div className="warningContent">{getWarningContent(inconsistencyWarnings)}</div>
</Tooltip>
}
>
<div className={classNames('warning')}>
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
};
const getSeriesInformation = ( const getSeriesInformation = (
SeriesNumber, SeriesNumber,
InstanceNumber, InstanceNumber,
numImageFrames numImageFrames,
inconsistencyWarnings
) => { ) => {
if (!SeriesNumber && !InstanceNumber && !numImageFrames) { if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
return; return;
} }
const seriesInformation =
return (
<div className="series-information"> <div className="series-information">
{getInfo(SeriesNumber, 'S:')} {getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')} {getInfo(InstanceNumber, 'I:')}
{getInfo(numImageFrames, '', 'image-frames')} {getInfo(numImageFrames, '', 'image-frames')}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
</div> </div>
);
return (seriesInformation);
}; };
return ( return (
<div className={classNames('series-details', { 'info-only': infoOnly })}> <div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div> <div className="series-description">{SeriesDescription}</div>
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames)} {getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, inconsistencyWarnings)}
</div> </div>
); );
} }
@@ -60,6 +123,7 @@ function Thumbnail(props) {
numImageFrames, numImageFrames,
SeriesDescription, SeriesDescription,
SeriesNumber, SeriesNumber,
hasWarnings,
stackPercentComplete, stackPercentComplete,
StudyInstanceUID, StudyInstanceUID,
onClick, onClick,
@@ -95,6 +159,7 @@ function Thumbnail(props) {
{/* SHOW IMAGE */} {/* SHOW IMAGE */}
{hasImage && ( {hasImage && (
<ImageThumbnail <ImageThumbnail
active={active}
imageSrc={imageSrc} imageSrc={imageSrc}
imageId={imageId} imageId={imageId}
error={error} error={error}
@@ -126,13 +191,14 @@ Thumbnail.propTypes = {
stackPercentComplete: PropTypes.number, stackPercentComplete: PropTypes.number,
/** /**
altImageText will be used when no imageId or imageSrc is provided. altImageText will be used when no imageId or imageSrc is provided.
It will be displayed inside the <div>. This is useful when it is difficult It will be displayed inside the <div>. This is useful when it is difficult
to make a preview for a type of DICOM series (e.g. DICOM-SR) to make a preview for a type of DICOM series (e.g. DICOM-SR)
*/ */
altImageText: PropTypes.string, altImageText: PropTypes.string,
SeriesDescription: PropTypes.string, SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hasWarnings: PropTypes.instanceOf(Promise),
numImageFrames: PropTypes.number, numImageFrames: PropTypes.number,
onDoubleClick: PropTypes.func, onDoubleClick: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,
@@ -2,8 +2,15 @@
:root { :root {
--series-count-background-color: #678696; --series-count-background-color: #678696;
--active-color: #20A5D6;
} }
.thumbnail.active
.alt-image-text
border-color: var(--active-color);
box-shadow: none
transition: var(--sidebar-transition);
.thumbnail .thumbnail
cursor: pointer cursor: pointer
display: table display: table
@@ -16,7 +23,7 @@
justify-content: center; justify-content: center;
background-color: var(--primary-background-color); background-color: var(--primary-background-color);
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark); box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
border: 5px solid transparent; border: 2px solid transparent;
border-radius: 12px; border-radius: 12px;
height: 135px; height: 135px;
margin: 0 auto; margin: 0 auto;
@@ -78,6 +85,17 @@
height: 11px height: 11px
width: 11px width: 11px
.warning
margin: auto 0;
opacity: 1
color: #e29e4a;
svg
width: 16px;
height: 14px;
pointer-events: inherit;
.value .value
color: var(--text-secondary-color); color: var(--text-secondary-color);
display: inline-block display: inline-block
@@ -0,0 +1,104 @@
import React, { useState, createContext, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
const LoggerContext = createContext(null);
const { Provider } = LoggerContext;
export const useLogger = () => useContext(LoggerContext);
const LoggerProvider = ({ children, service }) => {
const [state, setState] = useState({
errors: [],
infos: [],
});
useEffect(() => {
const onErrorHandler = ({ error: errorObject, message }) => {
error({ error: errorObject, message });
};
window.addEventListener('error', onErrorHandler);
return () => {
window.removeEventListener('error', onErrorHandler);
};
}, []);
/**
* Logs an error
*
* @param {object} props { error, stack, message, displayOnConsole }
* @returns void
*/
const error = ({
error = {},
stack = '',
message = '',
displayOnConsole = true,
}) => {
const errorObject = { error, stack, message, displayOnConsole };
setState(state => ({ ...state, errors: [...state.errors, errorObject] }));
if (displayOnConsole) {
console.error(error);
}
};
/**
* Logs an info
*
* @param {object} props { message, displayOnConsole }
* @returns void
*/
const info = ({ message = '', displayOnConsole = true }) => {
setState(state => ({
...state,
infos: state.infos.push({ message, displayOnConsole }),
}));
if (displayOnConsole) {
console.info(message);
}
};
/**
* Sets the implementation of a log service that can be used by extensions
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ error, info });
}
}, [error, service, info]);
return <Provider value={{ info, error, state }}>{children}</Provider>;
};
/**
* Higher Order Component to use the log methods through a Class Component
*
* @returns
*/
export const withLogger = Component => {
return function WrappedComponent(props) {
const { error, info, state } = useLogger();
return <Component {...props} logger={{ error, info, state }} />;
};
};
LoggerProvider.defaultProps = {
service: null,
};
LoggerProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
};
export default LoggerProvider;
export const LogConsumer = LoggerContext.Consumer;
@@ -18,3 +18,8 @@ export {
withDialog, withDialog,
useDialog, useDialog,
} from './DialogProvider.js'; } from './DialogProvider.js';
export {
default as LoggerProvider,
withLogger,
useLogger,
} from './LoggerProvider.js';
+8 -2
View File
@@ -30,7 +30,7 @@ import {
AboutContent, AboutContent,
OHIFModal, OHIFModal,
ErrorBoundary, ErrorBoundary,
ErrorPage ErrorPage,
} from './components'; } from './components';
import { useDebounce, useMedia } from './hooks'; import { useDebounce, useMedia } from './hooks';
@@ -66,6 +66,9 @@ import {
ModalConsumer, ModalConsumer,
useModal, useModal,
withModal, withModal,
LoggerProvider,
withLogger,
useLogger,
} from './contextProviders'; } from './contextProviders';
export { export {
@@ -127,11 +130,14 @@ export {
useDialog, useDialog,
ErrorBoundary, ErrorBoundary,
ErrorPage, ErrorPage,
LoggerProvider,
withLogger,
useLogger,
// Hooks // Hooks
useDebounce, useDebounce,
useMedia, useMedia,
// Utils // Utils
ViewerbaseDragDropContext, ViewerbaseDragDropContext,
asyncComponent, asyncComponent,
retryImport retryImport,
}; };
+7
View File
@@ -41,6 +41,13 @@ module.exports = (env, argv) => {
path: DIST_DIR, path: DIST_DIR,
filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js', filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js',
publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix
devtoolModuleFilenameTemplate: function(info) {
if (isProdBuild) {
return `webpack:///${info.resourcePath}`;
} else {
return 'file:///' + encodeURI(info.absoluteResourcePath);
}
},
}, },
module: { module: {
rules: [...extractStyleChunksRule(isProdBuild)], rules: [...extractStyleChunksRule(isProdBuild)],
+221
View File
@@ -3,6 +3,227 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.9.22](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.21...@ohif/viewer@4.9.22) (2021-08-19)
### Bug Fixes
* Remove internet dependency for workbox (addressing [#2109](https://github.com/OHIF/Viewers/issues/2109)) ([#2151](https://github.com/OHIF/Viewers/issues/2151)) ([5f92f6e](https://github.com/OHIF/Viewers/commit/5f92f6ec7faa11d3fdc33ef8f22971a0bab87a36))
## [4.9.21](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.20...@ohif/viewer@4.9.21) (2021-07-26)
**Note:** Version bump only for package @ohif/viewer
## [4.9.20](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.19...@ohif/viewer@4.9.20) (2021-06-03)
**Note:** Version bump only for package @ohif/viewer
## [4.9.19](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.18...@ohif/viewer@4.9.19) (2021-06-03)
**Note:** Version bump only for package @ohif/viewer
## [4.9.18](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.17...@ohif/viewer@4.9.18) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [4.9.17](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.16...@ohif/viewer@4.9.17) (2021-05-17)
**Note:** Version bump only for package @ohif/viewer
## [4.9.16](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.15...@ohif/viewer@4.9.16) (2021-05-14)
**Note:** Version bump only for package @ohif/viewer
## [4.9.15](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.14...@ohif/viewer@4.9.15) (2021-04-22)
**Note:** Version bump only for package @ohif/viewer
## [4.9.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.13...@ohif/viewer@4.9.14) (2021-04-22)
**Note:** Version bump only for package @ohif/viewer
## [4.9.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.12...@ohif/viewer@4.9.13) (2021-04-21)
**Note:** Version bump only for package @ohif/viewer
## [4.9.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.11...@ohif/viewer@4.9.12) (2021-04-21)
**Note:** Version bump only for package @ohif/viewer
## [4.9.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.10...@ohif/viewer@4.9.11) (2021-04-21)
**Note:** Version bump only for package @ohif/viewer
## [4.9.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.9...@ohif/viewer@4.9.10) (2021-04-16)
**Note:** Version bump only for package @ohif/viewer
## [4.9.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.8...@ohif/viewer@4.9.9) (2021-04-15)
**Note:** Version bump only for package @ohif/viewer
## [4.9.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.7...@ohif/viewer@4.9.8) (2021-03-31)
**Note:** Version bump only for package @ohif/viewer
## [4.9.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.6...@ohif/viewer@4.9.7) (2021-03-26)
**Note:** Version bump only for package @ohif/viewer
## [4.9.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.5...@ohif/viewer@4.9.6) (2021-03-26)
**Note:** Version bump only for package @ohif/viewer
## [4.9.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.4...@ohif/viewer@4.9.5) (2021-03-25)
### Bug Fixes
* IDC-2288: Update toolbar tabs after derived displayset loaded ([#2307](https://github.com/OHIF/Viewers/issues/2307)) ([6e723eb](https://github.com/OHIF/Viewers/commit/6e723ebced58852007ca553792c740976cfc4201))
## [4.9.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.3...@ohif/viewer@4.9.4) (2021-03-19)
**Note:** Version bump only for package @ohif/viewer
## [4.9.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.2...@ohif/viewer@4.9.3) (2021-03-15)
**Note:** Version bump only for package @ohif/viewer
## [4.9.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.1...@ohif/viewer@4.9.2) (2021-03-09)
**Note:** Version bump only for package @ohif/viewer
## [4.9.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.0...@ohif/viewer@4.9.1) (2021-03-09)
**Note:** Version bump only for package @ohif/viewer
# [4.9.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.12...@ohif/viewer@4.9.0) (2021-03-04)
### Features
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
## [4.8.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.11...@ohif/viewer@4.8.12) (2021-03-03)
**Note:** Version bump only for package @ohif/viewer
## [4.8.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.10...@ohif/viewer@4.8.11) (2021-02-25)
**Note:** Version bump only for package @ohif/viewer
## [4.8.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.9...@ohif/viewer@4.8.10) (2021-02-08)
**Note:** Version bump only for package @ohif/viewer
## [4.8.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.8...@ohif/viewer@4.8.9) (2021-02-05) ## [4.8.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.8...@ohif/viewer@4.8.9) (2021-02-05)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
@@ -91,7 +91,7 @@ describe('OHIF Cornerstone Hotkeys', () => {
':nth-child(2) > .viewport-wrapper > .viewport-element > .ViewportOrientationMarkers.noselect > .left-mid.orientation-marker' ':nth-child(2) > .viewport-wrapper > .viewport-element > .ViewportOrientationMarkers.noselect > .left-mid.orientation-marker'
).as('viewport2InfoMidLeft'); ).as('viewport2InfoMidLeft');
cy.get( cy.get(
':nth-child(2) > .viewport-wrapper > .viewport-element > .ViewportOverlay > div.bottom-right.overlay-element > div' ':nth-child(2) > .viewport-wrapper > .viewport-element > .OHIFCornerstoneViewportOverlay > div.bottom-right.overlay-element > div'
).as('viewport2InfoBottomRight'); ).as('viewport2InfoBottomRight');
// Press multiples hotkeys on viewport #2 // Press multiples hotkeys on viewport #2
@@ -741,7 +741,7 @@ describe('OHIF User Preferences', () => {
// Overlay information from 2nd viewport // Overlay information from 2nd viewport
let second_viewport_overlay = let second_viewport_overlay =
'div:nth-child(2) > div > div.viewport-element > div.ViewportOverlay > div.bottom-right.overlay-element > div'; 'div:nth-child(2) > div > div.viewport-element > div.OHIFCornerstoneViewportOverlay > div.bottom-right.overlay-element > div';
// Shift active viewport to Viewport #2 // Shift active viewport to Viewport #2
cy.get('body').type('{rightarrow}'); cy.get('body').type('{rightarrow}');
+2 -2
View File
@@ -25,10 +25,10 @@ export function initCommonElementsAliases() {
'.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton' '.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
).as('seriesBtn'); ).as('seriesBtn');
cy.get('section.sidepanel.from-left').as('seriesPanel'); cy.get('section.sidepanel.from-left').as('seriesPanel');
cy.get('div.ViewportOverlay > div.bottom-left.overlay-element > div').as( cy.get('div.OHIFCornerstoneViewportOverlay > div.bottom-left.overlay-element > div').as(
'viewportInfoBottomLeft' 'viewportInfoBottomLeft'
); );
cy.get('div.ViewportOverlay > div.bottom-right.overlay-element > div').as( cy.get('div.OHIFCornerstoneViewportOverlay > div.bottom-right.overlay-element > div').as(
'viewportInfoBottomRight' 'viewportInfoBottomRight'
); );
cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft'); cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft');
+12 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "4.8.9", "version": "4.9.22",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -49,18 +49,18 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.12.3", "@ohif/core": "^2.13.13",
"@ohif/extension-cornerstone": "^2.10.1", "@ohif/extension-cornerstone": "^2.10.10",
"@ohif/extension-debugging": "^0.1.6", "@ohif/extension-debugging": "^0.2.2",
"@ohif/extension-dicom-html": "^1.3.1", "@ohif/extension-dicom-html": "^1.3.10",
"@ohif/extension-dicom-microscopy": "^0.51.4", "@ohif/extension-dicom-microscopy": "^0.52.0",
"@ohif/extension-dicom-pdf": "^1.0.6", "@ohif/extension-dicom-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.6.4", "@ohif/extension-dicom-rt": "^0.6.13",
"@ohif/extension-dicom-segmentation": "^0.5.2", "@ohif/extension-dicom-segmentation": "^0.6.10",
"@ohif/extension-lesion-tracker": "^0.2.1", "@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.10.4", "@ohif/extension-vtk": "^1.11.14",
"@ohif/i18n": "^0.53.0", "@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.8.2", "@ohif/ui": "^1.9.2",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
@@ -68,9 +68,9 @@
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.17.2", "dcmjs": "0.18.8",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4", "dicomweb-client": "^0.8.1",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
+1
View File
@@ -4,6 +4,7 @@ window.config = {
extensions: [], extensions: [],
showStudyList: true, showStudyList: true,
filterQueryParam: false, filterQueryParam: false,
disableServersCache: false,
servers: { servers: {
dicomWeb: [ dicomWeb: [
{ {
+12 -1
View File
@@ -3,7 +3,17 @@
// modules, so it's perfectly fine to serve this code to any browsers // modules, so it's perfectly fine to serve this code to any browsers
// (older browsers will just ignore it) // (older browsers will just ignore it)
// //
import { Workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-window.prod.mjs';
// The workbox code is locally stored and can be updated
// by running `npx workbox-cli copyLibraries platform/viewer/public/third_party/workbox`
// from the root directory.
// Since the workbox version may change with copyLibraries ensure that the
// right version is used here and in `service-worker.js`
import { Workbox } from './third_party/workbox/workbox-v5.1.4/workbox-window.dev.mjs';
// Alternatively use the internet version by commenting out the above import and
// replace with this line and do the corresponding change in service-worker.js
// import { Workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-window.prod.mjs';
var supportsServiceWorker = 'serviceWorker' in navigator; var supportsServiceWorker = 'serviceWorker' in navigator;
var isNotLocalDevelopment = var isNotLocalDevelopment =
@@ -11,6 +21,7 @@ var isNotLocalDevelopment =
if (supportsServiceWorker && isNotLocalDevelopment) { if (supportsServiceWorker && isNotLocalDevelopment) {
const swFileLocation = (window.PUBLIC_URL || '/') + 'sw.js'; const swFileLocation = (window.PUBLIC_URL || '/') + 'sw.js';
const wb = new Workbox(swFileLocation); const wb = new Workbox(swFileLocation);
// Add an event listener to detect when the registered // Add an event listener to detect when the registered
@@ -0,0 +1,818 @@
this.workbox = this.workbox || {};
this.workbox.backgroundSync = (function (exports, WorkboxError_js, logger_js, assert_js, getFriendlyURL_js, DBWrapper_js) {
'use strict';
try {
self['workbox:background-sync:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const DB_VERSION = 3;
const DB_NAME = 'workbox-background-sync';
const OBJECT_STORE_NAME = 'requests';
const INDEXED_PROP = 'queueName';
/**
* A class to manage storing requests from a Queue in IndexedDB,
* indexed by their queue name for easier access.
*
* @private
*/
class QueueStore {
/**
* Associates this instance with a Queue instance, so entries added can be
* identified by their queue name.
*
* @param {string} queueName
* @private
*/
constructor(queueName) {
this._queueName = queueName;
this._db = new DBWrapper_js.DBWrapper(DB_NAME, DB_VERSION, {
onupgradeneeded: this._upgradeDb
});
}
/**
* Append an entry last in the queue.
*
* @param {Object} entry
* @param {Object} entry.requestData
* @param {number} [entry.timestamp]
* @param {Object} [entry.metadata]
* @private
*/
async pushEntry(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'pushEntry',
paramName: 'entry'
});
assert_js.assert.isType(entry.requestData, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'pushEntry',
paramName: 'entry.requestData'
});
} // Don't specify an ID since one is automatically generated.
delete entry.id;
entry.queueName = this._queueName;
await this._db.add(OBJECT_STORE_NAME, entry);
}
/**
* Prepend an entry first in the queue.
*
* @param {Object} entry
* @param {Object} entry.requestData
* @param {number} [entry.timestamp]
* @param {Object} [entry.metadata]
* @private
*/
async unshiftEntry(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'unshiftEntry',
paramName: 'entry'
});
assert_js.assert.isType(entry.requestData, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'unshiftEntry',
paramName: 'entry.requestData'
});
}
const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
count: 1
});
if (firstEntry) {
// Pick an ID one less than the lowest ID in the object store.
entry.id = firstEntry.id - 1;
} else {
// Otherwise let the auto-incrementor assign the ID.
delete entry.id;
}
entry.queueName = this._queueName;
await this._db.add(OBJECT_STORE_NAME, entry);
}
/**
* Removes and returns the last entry in the queue matching the `queueName`.
*
* @return {Promise<Object>}
* @private
*/
async popEntry() {
return this._removeEntry({
direction: 'prev'
});
}
/**
* Removes and returns the first entry in the queue matching the `queueName`.
*
* @return {Promise<Object>}
* @private
*/
async shiftEntry() {
return this._removeEntry({
direction: 'next'
});
}
/**
* Returns all entries in the store matching the `queueName`.
*
* @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}
* @return {Promise<Array<Object>>}
* @private
*/
async getAll() {
return await this._db.getAllMatching(OBJECT_STORE_NAME, {
index: INDEXED_PROP,
query: IDBKeyRange.only(this._queueName)
});
}
/**
* Deletes the entry for the given ID.
*
* WARNING: this method does not ensure the deleted enry belongs to this
* queue (i.e. matches the `queueName`). But this limitation is acceptable
* as this class is not publicly exposed. An additional check would make
* this method slower than it needs to be.
*
* @private
* @param {number} id
*/
async deleteEntry(id) {
await this._db.delete(OBJECT_STORE_NAME, id);
}
/**
* Removes and returns the first or last entry in the queue (based on the
* `direction` argument) matching the `queueName`.
*
* @return {Promise<Object>}
* @private
*/
async _removeEntry({
direction
}) {
const [entry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
direction,
index: INDEXED_PROP,
query: IDBKeyRange.only(this._queueName),
count: 1
});
if (entry) {
await this.deleteEntry(entry.id);
return entry;
}
}
/**
* Upgrades the database given an `upgradeneeded` event.
*
* @param {Event} event
* @private
*/
_upgradeDb(event) {
const db = event.target.result;
if (event.oldVersion > 0 && event.oldVersion < DB_VERSION) {
if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
db.deleteObjectStore(OBJECT_STORE_NAME);
}
}
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
autoIncrement: true,
keyPath: 'id'
});
objStore.createIndex(INDEXED_PROP, INDEXED_PROP, {
unique: false
});
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const serializableProperties = ['method', 'referrer', 'referrerPolicy', 'mode', 'credentials', 'cache', 'redirect', 'integrity', 'keepalive'];
/**
* A class to make it easier to serialize and de-serialize requests so they
* can be stored in IndexedDB.
*
* @private
*/
class StorableRequest {
/**
* Accepts an object of request data that can be used to construct a
* `Request` but can also be stored in IndexedDB.
*
* @param {Object} requestData An object of request data that includes the
* `url` plus any relevant properties of
* [requestInit]{@link https://fetch.spec.whatwg.org/#requestinit}.
* @private
*/
constructor(requestData) {
{
assert_js.assert.isType(requestData, 'object', {
moduleName: 'workbox-background-sync',
className: 'StorableRequest',
funcName: 'constructor',
paramName: 'requestData'
});
assert_js.assert.isType(requestData.url, 'string', {
moduleName: 'workbox-background-sync',
className: 'StorableRequest',
funcName: 'constructor',
paramName: 'requestData.url'
});
} // If the request's mode is `navigate`, convert it to `same-origin` since
// navigation requests can't be constructed via script.
if (requestData['mode'] === 'navigate') {
requestData['mode'] = 'same-origin';
}
this._requestData = requestData;
}
/**
* Converts a Request object to a plain object that can be structured
* cloned or JSON-stringified.
*
* @param {Request} request
* @return {Promise<StorableRequest>}
*
* @private
*/
static async fromRequest(request) {
const requestData = {
url: request.url,
headers: {}
}; // Set the body if present.
if (request.method !== 'GET') {
// Use ArrayBuffer to support non-text request bodies.
// NOTE: we can't use Blobs becuse Safari doesn't support storing
// Blobs in IndexedDB in some cases:
// https://github.com/dfahlander/Dexie.js/issues/618#issuecomment-398348457
requestData.body = await request.clone().arrayBuffer();
} // Convert the headers from an iterable to an object.
for (const [key, value] of request.headers.entries()) {
requestData.headers[key] = value;
} // Add all other serializable request properties
for (const prop of serializableProperties) {
if (request[prop] !== undefined) {
requestData[prop] = request[prop];
}
}
return new StorableRequest(requestData);
}
/**
* Returns a deep clone of the instances `_requestData` object.
*
* @return {Object}
*
* @private
*/
toObject() {
const requestData = Object.assign({}, this._requestData);
requestData.headers = Object.assign({}, this._requestData.headers);
if (requestData.body) {
requestData.body = requestData.body.slice(0);
}
return requestData;
}
/**
* Converts this instance to a Request.
*
* @return {Request}
*
* @private
*/
toRequest() {
return new Request(this._requestData.url, this._requestData);
}
/**
* Creates and returns a deep clone of the instance.
*
* @return {StorableRequest}
*
* @private
*/
clone() {
return new StorableRequest(this.toObject());
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const TAG_PREFIX = 'workbox-background-sync';
const MAX_RETENTION_TIME = 60 * 24 * 7; // 7 days in minutes
const queueNames = new Set();
/**
* Converts a QueueStore entry into the format exposed by Queue. This entails
* converting the request data into a real request and omitting the `id` and
* `queueName` properties.
*
* @param {Object} queueStoreEntry
* @return {Object}
* @private
*/
const convertEntry = queueStoreEntry => {
const queueEntry = {
request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
timestamp: queueStoreEntry.timestamp
};
if (queueStoreEntry.metadata) {
queueEntry.metadata = queueStoreEntry.metadata;
}
return queueEntry;
};
/**
* A class to manage storing failed requests in IndexedDB and retrying them
* later. All parts of the storing and replaying process are observable via
* callbacks.
*
* @memberof module:workbox-background-sync
*/
class Queue {
/**
* Creates an instance of Queue with the given options
*
* @param {string} name The unique name for this queue. This name must be
* unique as it's used to register sync events and store requests
* in IndexedDB specific to this instance. An error will be thrown if
* a duplicate name is detected.
* @param {Object} [options]
* @param {Function} [options.onSync] A function that gets invoked whenever
* the 'sync' event fires. The function is invoked with an object
* containing the `queue` property (referencing this instance), and you
* can use the callback to customize the replay behavior of the queue.
* When not set the `replayRequests()` method is called.
* Note: if the replay fails after a sync event, make sure you throw an
* error, so the browser knows to retry the sync event later.
* @param {number} [options.maxRetentionTime=7 days] The amount of time (in
* minutes) a request may be retried. After this amount of time has
* passed, the request will be deleted from the queue.
*/
constructor(name, {
onSync,
maxRetentionTime
} = {}) {
this._syncInProgress = false;
this._requestsAddedDuringSync = false; // Ensure the store name is not already being used
if (queueNames.has(name)) {
throw new WorkboxError_js.WorkboxError('duplicate-queue-name', {
name
});
} else {
queueNames.add(name);
}
this._name = name;
this._onSync = onSync || this.replayRequests;
this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
this._queueStore = new QueueStore(this._name);
this._addSyncListener();
}
/**
* @return {string}
*/
get name() {
return this._name;
}
/**
* Stores the passed request in IndexedDB (with its timestamp and any
* metadata) at the end of the queue.
*
* @param {Object} entry
* @param {Request} entry.request The request to store in the queue.
* @param {Object} [entry.metadata] Any metadata you want associated with the
* stored request. When requests are replayed you'll have access to this
* metadata object in case you need to modify the request beforehand.
* @param {number} [entry.timestamp] The timestamp (Epoch time in
* milliseconds) when the request was first added to the queue. This is
* used along with `maxRetentionTime` to remove outdated requests. In
* general you don't need to set this value, as it's automatically set
* for you (defaulting to `Date.now()`), but you can update it if you
* don't want particular requests to expire.
*/
async pushRequest(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'pushRequest',
paramName: 'entry'
});
assert_js.assert.isInstance(entry.request, Request, {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'pushRequest',
paramName: 'entry.request'
});
}
await this._addRequest(entry, 'push');
}
/**
* Stores the passed request in IndexedDB (with its timestamp and any
* metadata) at the beginning of the queue.
*
* @param {Object} entry
* @param {Request} entry.request The request to store in the queue.
* @param {Object} [entry.metadata] Any metadata you want associated with the
* stored request. When requests are replayed you'll have access to this
* metadata object in case you need to modify the request beforehand.
* @param {number} [entry.timestamp] The timestamp (Epoch time in
* milliseconds) when the request was first added to the queue. This is
* used along with `maxRetentionTime` to remove outdated requests. In
* general you don't need to set this value, as it's automatically set
* for you (defaulting to `Date.now()`), but you can update it if you
* don't want particular requests to expire.
*/
async unshiftRequest(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'unshiftRequest',
paramName: 'entry'
});
assert_js.assert.isInstance(entry.request, Request, {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'unshiftRequest',
paramName: 'entry.request'
});
}
await this._addRequest(entry, 'unshift');
}
/**
* Removes and returns the last request in the queue (along with its
* timestamp and any metadata). The returned object takes the form:
* `{request, timestamp, metadata}`.
*
* @return {Promise<Object>}
*/
async popRequest() {
return this._removeRequest('pop');
}
/**
* Removes and returns the first request in the queue (along with its
* timestamp and any metadata). The returned object takes the form:
* `{request, timestamp, metadata}`.
*
* @return {Promise<Object>}
*/
async shiftRequest() {
return this._removeRequest('shift');
}
/**
* Returns all the entries that have not expired (per `maxRetentionTime`).
* Any expired entries are removed from the queue.
*
* @return {Promise<Array<Object>>}
*/
async getAll() {
const allEntries = await this._queueStore.getAll();
const now = Date.now();
const unexpiredEntries = [];
for (const entry of allEntries) {
// Ignore requests older than maxRetentionTime. Call this function
// recursively until an unexpired request is found.
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
if (now - entry.timestamp > maxRetentionTimeInMs) {
await this._queueStore.deleteEntry(entry.id);
} else {
unexpiredEntries.push(convertEntry(entry));
}
}
return unexpiredEntries;
}
/**
* Adds the entry to the QueueStore and registers for a sync event.
*
* @param {Object} entry
* @param {Request} entry.request
* @param {Object} [entry.metadata]
* @param {number} [entry.timestamp=Date.now()]
* @param {string} operation ('push' or 'unshift')
* @private
*/
async _addRequest({
request,
metadata,
timestamp = Date.now()
}, operation) {
const storableRequest = await StorableRequest.fromRequest(request.clone());
const entry = {
requestData: storableRequest.toObject(),
timestamp
}; // Only include metadata if it's present.
if (metadata) {
entry.metadata = metadata;
}
await this._queueStore[`${operation}Entry`](entry);
{
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
} // Don't register for a sync if we're in the middle of a sync. Instead,
// we wait until the sync is complete and call register if
// `this._requestsAddedDuringSync` is true.
if (this._syncInProgress) {
this._requestsAddedDuringSync = true;
} else {
await this.registerSync();
}
}
/**
* Removes and returns the first or last (depending on `operation`) entry
* from the QueueStore that's not older than the `maxRetentionTime`.
*
* @param {string} operation ('pop' or 'shift')
* @return {Object|undefined}
* @private
*/
async _removeRequest(operation) {
const now = Date.now();
const entry = await this._queueStore[`${operation}Entry`]();
if (entry) {
// Ignore requests older than maxRetentionTime. Call this function
// recursively until an unexpired request is found.
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
if (now - entry.timestamp > maxRetentionTimeInMs) {
return this._removeRequest(operation);
}
return convertEntry(entry);
} else {
return undefined;
}
}
/**
* Loops through each request in the queue and attempts to re-fetch it.
* If any request fails to re-fetch, it's put back in the same position in
* the queue (which registers a retry for the next sync event).
*/
async replayRequests() {
let entry;
while (entry = await this.shiftRequest()) {
try {
await fetch(entry.request.clone());
if ("dev" !== 'production') {
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(entry.request.url)}'` + `has been replayed in queue '${this._name}'`);
}
} catch (error) {
await this.unshiftRequest(entry);
{
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(entry.request.url)}'` + `failed to replay, putting it back in queue '${this._name}'`);
}
throw new WorkboxError_js.WorkboxError('queue-replay-failed', {
name: this._name
});
}
}
{
logger_js.logger.log(`All requests in queue '${this.name}' have successfully ` + `replayed; the queue is now empty!`);
}
}
/**
* Registers a sync event with a tag unique to this instance.
*/
async registerSync() {
if ('sync' in self.registration) {
try {
await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
} catch (err) {
// This means the registration failed for some reason, possibly due to
// the user disabling it.
{
logger_js.logger.warn(`Unable to register sync event for '${this._name}'.`, err);
}
}
}
}
/**
* In sync-supporting browsers, this adds a listener for the sync event.
* In non-sync-supporting browsers, this will retry the queue on service
* worker startup.
*
* @private
*/
_addSyncListener() {
if ('sync' in self.registration) {
self.addEventListener('sync', event => {
if (event.tag === `${TAG_PREFIX}:${this._name}`) {
{
logger_js.logger.log(`Background sync for tag '${event.tag}'` + `has been received`);
}
const syncComplete = async () => {
this._syncInProgress = true;
let syncError;
try {
await this._onSync({
queue: this
});
} catch (error) {
syncError = error; // Rethrow the error. Note: the logic in the finally clause
// will run before this gets rethrown.
throw syncError;
} finally {
// New items may have been added to the queue during the sync,
// so we need to register for a new sync if that's happened...
// Unless there was an error during the sync, in which
// case the browser will automatically retry later, as long
// as `event.lastChance` is not true.
if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
await this.registerSync();
}
this._syncInProgress = false;
this._requestsAddedDuringSync = false;
}
};
event.waitUntil(syncComplete());
}
});
} else {
{
logger_js.logger.log(`Background sync replaying without background sync event`);
} // If the browser doesn't support background sync, retry
// every time the service worker starts up as a fallback.
this._onSync({
queue: this
});
}
}
/**
* Returns the set of queue names. This is primarily used to reset the list
* of queue names in tests.
*
* @return {Set}
*
* @private
*/
static get _queueNames() {
return queueNames;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* A class implementing the `fetchDidFail` lifecycle callback. This makes it
* easier to add failed requests to a background sync Queue.
*
* @memberof module:workbox-background-sync
*/
class BackgroundSyncPlugin {
/**
* @param {string} name See the [Queue]{@link module:workbox-background-sync.Queue}
* documentation for parameter details.
* @param {Object} [options] See the
* [Queue]{@link module:workbox-background-sync.Queue} documentation for
* parameter details.
*/
constructor(name, options) {
/**
* @param {Object} options
* @param {Request} options.request
* @private
*/
this.fetchDidFail = async ({
request
}) => {
await this._queue.pushRequest({
request
});
};
this._queue = new Queue(name, options);
}
}
exports.BackgroundSyncPlugin = BackgroundSyncPlugin;
exports.Queue = Queue;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-background-sync.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-background-sync.dev.js","sources":["../_version.js","../lib/QueueStore.js","../lib/StorableRequest.js","../Queue.js","../BackgroundSyncPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:background-sync:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport '../_version.js';\nconst DB_VERSION = 3;\nconst DB_NAME = 'workbox-background-sync';\nconst OBJECT_STORE_NAME = 'requests';\nconst INDEXED_PROP = 'queueName';\n/**\n * A class to manage storing requests from a Queue in IndexedDB,\n * indexed by their queue name for easier access.\n *\n * @private\n */\nexport class QueueStore {\n /**\n * Associates this instance with a Queue instance, so entries added can be\n * identified by their queue name.\n *\n * @param {string} queueName\n * @private\n */\n constructor(queueName) {\n this._queueName = queueName;\n this._db = new DBWrapper(DB_NAME, DB_VERSION, {\n onupgradeneeded: this._upgradeDb,\n });\n }\n /**\n * Append an entry last in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async pushEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry.requestData',\n });\n }\n // Don't specify an ID since one is automatically generated.\n delete entry.id;\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Prepend an entry first in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async unshiftEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry.requestData',\n });\n }\n const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {\n count: 1,\n });\n if (firstEntry) {\n // Pick an ID one less than the lowest ID in the object store.\n entry.id = firstEntry.id - 1;\n }\n else {\n // Otherwise let the auto-incrementor assign the ID.\n delete entry.id;\n }\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Removes and returns the last entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async popEntry() {\n return this._removeEntry({ direction: 'prev' });\n }\n /**\n * Removes and returns the first entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async shiftEntry() {\n return this._removeEntry({ direction: 'next' });\n }\n /**\n * Returns all entries in the store matching the `queueName`.\n *\n * @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}\n * @return {Promise<Array<Object>>}\n * @private\n */\n async getAll() {\n return await this._db.getAllMatching(OBJECT_STORE_NAME, {\n index: INDEXED_PROP,\n query: IDBKeyRange.only(this._queueName),\n });\n }\n /**\n * Deletes the entry for the given ID.\n *\n * WARNING: this method does not ensure the deleted enry belongs to this\n * queue (i.e. matches the `queueName`). But this limitation is acceptable\n * as this class is not publicly exposed. An additional check would make\n * this method slower than iLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.backgroundSync=function(t,e,s,i,n){"use strict";try{self["workbox:background-sync:5.1.4"]&&_()}catch(t){}class a{constructor(t){this.t=t,this.s=new n.DBWrapper("workbox-background-sync",3,{onupgradeneeded:this.i})}async pushEntry(t){delete t.id,t.queueName=this.t,await this.s.add("requests",t)}async unshiftEntry(t){const[e]=await this.s.getAllMatching("requests",{count:1});e?t.id=e.id-1:delete t.id,t.queueName=this.t,await this.s.add("requests",t)}async popEntry(){return this.h({direction:"prev"})}async shiftEntry(){return this.h({direction:"next"})}async getAll(){return await this.s.getAllMatching("requests",{index:"queueName",query:IDBKeyRange.only(this.t)})}async deleteEntry(t){await this.s.delete("requests",t)}async h({direction:t}){const[e]=await this.s.getAllMatching("requests",{direction:t,index:"queueName",query:IDBKeyRange.only(this.t),count:1});if(e)return await this.deleteEntry(e.id),e}i(t){const e=t.target.result;t.oldVersion>0&&t.oldVersion<3&&e.objectStoreNames.contains("requests")&&e.deleteObjectStore("requests");e.createObjectStore("requests",{autoIncrement:!0,keyPath:"id"}).createIndex("queueName","queueName",{unique:!1})}}const r=["method","referrer","referrerPolicy","mode","credentials","cache","redirect","integrity","keepalive"];class c{constructor(t){"navigate"===t.mode&&(t.mode="same-origin"),this.u=t}static async fromRequest(t){const e={url:t.url,headers:{}};"GET"!==t.method&&(e.body=await t.clone().arrayBuffer());for(const[s,i]of t.headers.entries())e.headers[s]=i;for(const s of r)void 0!==t[s]&&(e[s]=t[s]);return new c(e)}toObject(){const t=Object.assign({},this.u);return t.headers=Object.assign({},this.u.headers),t.body&&(t.body=t.body.slice(0)),t}toRequest(){return new Request(this.u.url,this.u)}clone(){return new c(this.toObject())}}const h=new Set,u=t=>{const e={request:new c(t.requestData).toRequest(),timestamp:t.timestamp};return t.metadata&&(e.metadata=t.metadata),e};class o{constructor(t,{onSync:s,maxRetentionTime:i}={}){if(this.o=!1,this.q=!1,h.has(t))throw new e.WorkboxError("duplicate-queue-name",{name:t});h.add(t),this.l=t,this.m=s||this.replayRequests,this.p=i||10080,this.g=new a(this.l),this.R()}get name(){return this.l}async pushRequest(t){await this.k(t,"push")}async unshiftRequest(t){await this.k(t,"unshift")}async popRequest(){return this.D("pop")}async shiftRequest(){return this.D("shift")}async getAll(){const t=await this.g.getAll(),e=Date.now(),s=[];for(const i of t){const t=60*this.p*1e3;e-i.timestamp>t?await this.g.deleteEntry(i.id):s.push(u(i))}return s}async k({request:t,metadata:e,timestamp:s=Date.now()},i){const n={requestData:(await c.fromRequest(t.clone())).toObject(),timestamp:s};e&&(n.metadata=e),await this.g[i+"Entry"](n),this.o?this.q=!0:await this.registerSync()}async D(t){const e=Date.now(),s=await this.g[t+"Entry"]();if(s){const i=60*this.p*1e3;return e-s.timestamp>i?this.D(t):u(s)}}async replayRequests(){let t;for(;t=await this.shiftRequest();)try{await fetch(t.request.clone())}catch(s){throw await this.unshiftRequest(t),new e.WorkboxError("queue-replay-failed",{name:this.l})}}async registerSync(){if("sync"in self.registration)try{await self.registration.sync.register("workbox-background-sync:"+this.l)}catch(t){}}R(){"sync"in self.registration?self.addEventListener("sync",t=>{if(t.tag==="workbox-background-sync:"+this.l){const e=async()=>{let e;this.o=!0;try{await this.m({queue:this})}catch(t){throw e=t,e}finally{!this.q||e&&!t.lastChance||await this.registerSync(),this.o=!1,this.q=!1}};t.waitUntil(e())}}):this.m({queue:this})}static get _(){return h}}return t.BackgroundSyncPlugin=class{constructor(t,e){this.fetchDidFail=async({request:t})=>{await this.v.pushRequest({request:t})},this.v=new o(t,e)}},t.Queue=o,t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-background-sync.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-background-sync.prod.js","sources":["../_version.js","../lib/QueueStore.js","../lib/StorableRequest.js","../Queue.js","../BackgroundSyncPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:background-sync:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport '../_version.js';\nconst DB_VERSION = 3;\nconst DB_NAME = 'workbox-background-sync';\nconst OBJECT_STORE_NAME = 'requests';\nconst INDEXED_PROP = 'queueName';\n/**\n * A class to manage storing requests from a Queue in IndexedDB,\n * indexed by their queue name for easier access.\n *\n * @private\n */\nexport class QueueStore {\n /**\n * Associates this instance with a Queue instance, so entries added can be\n * identified by their queue name.\n *\n * @param {string} queueName\n * @private\n */\n constructor(queueName) {\n this._queueName = queueName;\n this._db = new DBWrapper(DB_NAME, DB_VERSION, {\n onupgradeneeded: this._upgradeDb,\n });\n }\n /**\n * Append an entry last in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async pushEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry.requestData',\n });\n }\n // Don't specify an ID since one is automatically generated.\n delete entry.id;\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Prepend an entry first in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async unshiftEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry.requestData',\n });\n }\n const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {\n count: 1,\n });\n if (firstEntry) {\n // Pick an ID one less than the lowest ID in the object store.\n entry.id = firstEntry.id - 1;\n }\n else {\n // Otherwise let the auto-incrementor assign the ID.\n delete entry.id;\n }\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Removes and returns the last entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async popEntry() {\n return this._removeEntry({ direction: 'prev' });\n }\n /**\n * Removes and returns the first entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async shiftEntry() {\n return this._removeEntry({ direction: 'next' });\n }\n /**\n * Returns all entries in the store matching the `queueName`.\n *\n * @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}\n * @return {Promise<Array<Object>>}\n * @private\n */\n async getAll() {\n return await this._db.getAllMatching(OBJECT_STORE_NAME, {\n index: INDEXED_PROP,\n query: IDBKeyRange.only(this._queueName),\n });\n }\n /**\n * Deletes the entry for the given ID.\n *\n * WARNING: this method does not ensure the deleted enry belongs to this\n * queue (i.e. matches the `queueName`). But this limitation is acceptable\n * as this class is not publicly exposed. An additional check would make\n * this method slower than Line truncated
@@ -0,0 +1,288 @@
this.workbox = this.workbox || {};
this.workbox.broadcastUpdate = (function (exports, assert_js, timeout_js, resultingClientExists_js, logger_js, WorkboxError_js, dontWaitFor_js) {
'use strict';
try {
self['workbox:broadcast-update:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Given two `Response's`, compares several header values to see if they are
* the same or not.
*
* @param {Response} firstResponse
* @param {Response} secondResponse
* @param {Array<string>} headersToCheck
* @return {boolean}
*
* @memberof module:workbox-broadcast-update
*/
const responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {
{
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
throw new WorkboxError_js.WorkboxError('invalid-responses-are-same-args');
}
}
const atLeastOneHeaderAvailable = headersToCheck.some(header => {
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
});
if (!atLeastOneHeaderAvailable) {
{
logger_js.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
logger_js.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
} // Just return true, indicating the that responses are the same, since we
// can't determine otherwise.
return true;
}
return headersToCheck.every(header => {
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
return headerStateComparison && headerValueComparison;
});
};
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';
const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
const DEFAULT_HEADERS_TO_CHECK = ['content-length', 'etag', 'last-modified'];
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
// TODO(philipwalton): remove once this Safari bug fix has been released.
// https://bugs.webkit.org/show_bug.cgi?id=201169
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
/**
* Generates the default payload used in update messages. By default the
* payload includes the `cacheName` and `updatedURL` fields.
*
* @return Object
* @private
*/
function defaultPayloadGenerator(data) {
return {
cacheName: data.cacheName,
updatedURL: data.request.url
};
}
/**
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
* response has been updated.
*
* For efficiency's sake, the underlying response bodies are not compared;
* only specific response headers are checked.
*
* @memberof module:workbox-broadcast-update
*/
class BroadcastCacheUpdate {
/**
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
* broadcast messages on
*
* @param {Object} options
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor({
headersToCheck,
generatePayload
} = {}) {
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
this._generatePayload = generatePayload || defaultPayloadGenerator;
}
/**
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and sends a message (via `postMessage()`) to all window clients if the
* responses differ (note: neither of the Responses can be
* {@link http://stackoverflow.com/questions/39109789|opaque}).
*
* The message that's posted has the following format (where `payload` can
* be customized via the `generatePayload` option the instance is created
* with):
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* @param {Object} options
* @param {Response} [options.oldResponse] Cached response to compare.
* @param {Response} options.newResponse Possibly updated response to compare.
* @param {Request} options.request The request.
* @param {string} options.cacheName Name of the cache the responses belong
* to. This is included in the broadcast message.
* @param {Event} [options.event] event An optional event that triggered
* this possible cache update.
* @return {Promise} Resolves once the update is sent.
*/
async notifyIfUpdated(options) {
{
assert_js.assert.isType(options.cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'cacheName'
});
assert_js.assert.isInstance(options.newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'newResponse'
});
assert_js.assert.isInstance(options.request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'request'
});
} // Without two responses there is nothing to compare.
if (!options.oldResponse) {
return;
}
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
{
logger_js.logger.log(`Newer response found (and cached) for:`, options.request.url);
}
const messageData = {
type: CACHE_UPDATED_MESSAGE_TYPE,
meta: CACHE_UPDATED_MESSAGE_META,
payload: this._generatePayload(options)
}; // For navigation requests, wait until the new window client exists
// before sending the message
if (options.request.mode === 'navigate') {
let resultingClientId;
if (options.event instanceof FetchEvent) {
resultingClientId = options.event.resultingClientId;
}
const resultingWin = await resultingClientExists_js.resultingClientExists(resultingClientId); // Safari does not currently implement postMessage buffering and
// there's no good way to feature detect that, so to increase the
// chances of the message being delivered in Safari, we add a timeout.
// We also do this if `resultingClientExists()` didn't return a client,
// which means it timed out, so it's worth waiting a bit longer.
if (!resultingWin || isSafari) {
// 3500 is chosen because (according to CrUX data) 80% of mobile
// websites hit the DOMContentLoaded event in less than 3.5 seconds.
// And presumably sites implementing service worker are on the
// higher end of the performance spectrum.
await timeout_js.timeout(3500);
}
}
const windows = await self.clients.matchAll({
type: 'window'
});
for (const win of windows) {
win.postMessage(messageData);
}
}
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This plugin will automatically broadcast a message whenever a cached response
* is updated.
*
* @memberof module:workbox-broadcast-update
*/
class BroadcastUpdatePlugin {
/**
* Construct a BroadcastCacheUpdate instance with the passed options and
* calls its [`notifyIfUpdated()`]{@link module:workbox-broadcast-update.BroadcastCacheUpdate~notifyIfUpdated}
* method whenever the plugin's `cacheDidUpdate` callback is invoked.
*
* @param {Object} options
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor(options) {
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-sw` and `workbox-runtime-caching` handlers when an entry is
* added to a cache.
*
* @private
* @param {Object} options The input object to this function.
* @param {string} options.cacheName Name of the cache being updated.
* @param {Response} [options.oldResponse] The previous cached value, if any.
* @param {Response} options.newResponse The new value in the cache.
* @param {Request} options.request The request that triggered the update.
* @param {Request} [options.event] The event that triggered the update.
*/
this.cacheDidUpdate = async options => {
dontWaitFor_js.dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));
};
this._broadcastUpdate = new BroadcastCacheUpdate(options);
}
}
exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
exports.BroadcastUpdatePlugin = BroadcastUpdatePlugin;
exports.responsesAreSame = responsesAreSame;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-broadcast-update.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-broadcast-update.dev.js","sources":["../_version.js","../responsesAreSame.js","../utils/constants.js","../BroadcastCacheUpdate.js","../BroadcastUpdatePlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:broadcast-update:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * Given two `Response's`, compares several header values to see if they are\n * the same or not.\n *\n * @param {Response} firstResponse\n * @param {Response} secondResponse\n * @param {Array<string>} headersToCheck\n * @return {boolean}\n *\n * @memberof module:workbox-broadcast-update\n */\nconst responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {\n if (process.env.NODE_ENV !== 'production') {\n if (!(firstResponse instanceof Response &&\n secondResponse instanceof Response)) {\n throw new WorkboxError('invalid-responses-are-same-args');\n }\n }\n const atLeastOneHeaderAvailable = headersToCheck.some((header) => {\n return firstResponse.headers.has(header) &&\n secondResponse.headers.has(header);\n });\n if (!atLeastOneHeaderAvailable) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`Unable to determine where the response has been updated ` +\n `because none of the headers that would be checked are present.`);\n logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);\n }\n // Just return true, indicating the that responses are the same, since we\n // can't determine otherwise.\n return true;\n }\n return headersToCheck.every((header) => {\n const headerStateComparison = firstResponse.headers.has(header) ===\n secondResponse.headers.has(header);\n const headerValueComparison = firstResponse.headers.get(header) ===\n secondResponse.headers.get(header);\n return headerStateComparison && headerValueComparison;\n });\n};\nexport { responsesAreSame };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';\nexport const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';\nexport const DEFAULT_HEADERS_TO_CHECK = [\n 'content-length',\n 'etag',\n 'last-modified',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { timeout } from 'workbox-core/_private/timeout.js';\nimport { resultingClientExists } from 'workbox-core/_private/resultingClientExists.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { responsesAreSame } from './responsesAreSame.js';\nimport { CACHE_UPDATED_MESSAGE_TYPE, CACHE_UPDATED_MESSAGE_META, DEFAULT_HEADERS_TO_CHECK } from './utils/constants.js';\nimport './_version.js';\n// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser\n// TODO(philipwalton): remove once this Safari bug fix has been released.\n// https://bugs.webkit.org/show_bug.cgi?id=201169\nconst isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n/**\n * Generates the default payload used in update messages. By default the\n * payload includes the `cacheName` and `updatedURL` fields.\n *\n * @return Object\n * @private\n */\nfunction defaultPayloadGenerator(data) {\n return {\n cacheName: data.cacheName,\n updatedURL: data.request.url,\n };\n}\n/**\n * Uses the `postMessage()` API to inform any open windows/tabs when a cached\n * response has been updated.\n *\n * For efficiency's sake, the underlying response bodies are not compared;\n * only specific response headers are checked.\n *\n * @memberof module:workbox-broadcast-update\n */\nclass BroadcastCacheUpdate {\n /**\n * Construct a BroadcastCacheUpdate instance with a specific `channelName` to\n * broadcast messages on\n *\n * @param {Object} options\n * @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n * A list of headers that will be used to determine whether the responses\n * differ.\n * @param {string} [options.generatePayload] A function whose return value\n * will be used as the `payload` field in any cache update messaLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(t,a,o,s){"use strict";try{self["workbox:broadcast-update:5.1.4"]&&_()}catch(t){}const e=(t,a,o)=>!o.some(o=>t.headers.has(o)&&a.headers.has(o))||o.every(o=>{const s=t.headers.has(o)===a.headers.has(o),e=t.headers.get(o)===a.headers.get(o);return s&&e}),n=["content-length","etag","last-modified"],i=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);function c(t){return{cacheName:t.cacheName,updatedURL:t.request.url}}class r{constructor({headersToCheck:t,generatePayload:a}={}){this.t=t||n,this.o=a||c}async notifyIfUpdated(t){if(t.oldResponse&&!e(t.oldResponse,t.newResponse,this.t)){const s={type:"CACHE_UPDATED",meta:"workbox-broadcast-update",payload:this.o(t)};if("navigate"===t.request.mode){let s;t.event instanceof FetchEvent&&(s=t.event.resultingClientId);await o.resultingClientExists(s)&&!i||await a.timeout(3500)}const e=await self.clients.matchAll({type:"window"});for(const t of e)t.postMessage(s)}}}return t.BroadcastCacheUpdate=r,t.BroadcastUpdatePlugin=class{constructor(t){this.cacheDidUpdate=async t=>{s.dontWaitFor(this.s.notifyIfUpdated(t))},this.s=new r(t)}},t.responsesAreSame=e,t}({},workbox.core._private,workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-broadcast-update.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-broadcast-update.prod.js","sources":["../_version.js","../responsesAreSame.js","../utils/constants.js","../BroadcastCacheUpdate.js","../BroadcastUpdatePlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:broadcast-update:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * Given two `Response's`, compares several header values to see if they are\n * the same or not.\n *\n * @param {Response} firstResponse\n * @param {Response} secondResponse\n * @param {Array<string>} headersToCheck\n * @return {boolean}\n *\n * @memberof module:workbox-broadcast-update\n */\nconst responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {\n if (process.env.NODE_ENV !== 'production') {\n if (!(firstResponse instanceof Response &&\n secondResponse instanceof Response)) {\n throw new WorkboxError('invalid-responses-are-same-args');\n }\n }\n const atLeastOneHeaderAvailable = headersToCheck.some((header) => {\n return firstResponse.headers.has(header) &&\n secondResponse.headers.has(header);\n });\n if (!atLeastOneHeaderAvailable) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`Unable to determine where the response has been updated ` +\n `because none of the headers that would be checked are present.`);\n logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);\n }\n // Just return true, indicating the that responses are the same, since we\n // can't determine otherwise.\n return true;\n }\n return headersToCheck.every((header) => {\n const headerStateComparison = firstResponse.headers.has(header) ===\n secondResponse.headers.has(header);\n const headerValueComparison = firstResponse.headers.get(header) ===\n secondResponse.headers.get(header);\n return headerStateComparison && headerValueComparison;\n });\n};\nexport { responsesAreSame };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';\nexport const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';\nexport const DEFAULT_HEADERS_TO_CHECK = [\n 'content-length',\n 'etag',\n 'last-modified',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { timeout } from 'workbox-core/_private/timeout.js';\nimport { resultingClientExists } from 'workbox-core/_private/resultingClientExists.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { responsesAreSame } from './responsesAreSame.js';\nimport { CACHE_UPDATED_MESSAGE_TYPE, CACHE_UPDATED_MESSAGE_META, DEFAULT_HEADERS_TO_CHECK } from './utils/constants.js';\nimport './_version.js';\n// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser\n// TODO(philipwalton): remove once this Safari bug fix has been released.\n// https://bugs.webkit.org/show_bug.cgi?id=201169\nconst isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n/**\n * Generates the default payload used in update messages. By default the\n * payload includes the `cacheName` and `updatedURL` fields.\n *\n * @return Object\n * @private\n */\nfunction defaultPayloadGenerator(data) {\n return {\n cacheName: data.cacheName,\n updatedURL: data.request.url,\n };\n}\n/**\n * Uses the `postMessage()` API to inform any open windows/tabs when a cached\n * response has been updated.\n *\n * For efficiency's sake, the underlying response bodies are not compared;\n * only specific response headers are checked.\n *\n * @memberof module:workbox-broadcast-update\n */\nclass BroadcastCacheUpdate {\n /**\n * Construct a BroadcastCacheUpdate instance with a specific `channelName` to\n * broadcast messages on\n *\n * @param {Object} options\n * @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n * A list of headers that will be used to determine whether the responses\n * differ.\n * @param {string} [options.generatePayload] A function whose return value\n * will be used as the `payload` field in any cache update messLine truncated
@@ -0,0 +1,191 @@
this.workbox = this.workbox || {};
this.workbox.cacheableResponse = (function (exports, assert_js, WorkboxError_js, getFriendlyURL_js, logger_js) {
'use strict';
try {
self['workbox:cacheable-response:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This class allows you to set up rules determining what
* status codes and/or headers need to be present in order for a
* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* to be considered cacheable.
*
* @memberof module:workbox-cacheable-response
*/
class CacheableResponse {
/**
* To construct a new CacheableResponse instance you must provide at least
* one of the `config` properties.
*
* If both `statuses` and `headers` are specified, then both conditions must
* be met for the `Response` to be considered cacheable.
*
* @param {Object} config
* @param {Array<number>} [config.statuses] One or more status codes that a
* `Response` can have and be considered cacheable.
* @param {Object<string,string>} [config.headers] A mapping of header names
* and expected values that a `Response` can have and be considered cacheable.
* If multiple headers are provided, only one needs to be present.
*/
constructor(config = {}) {
{
if (!(config.statuses || config.headers)) {
throw new WorkboxError_js.WorkboxError('statuses-or-headers-required', {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'constructor'
});
}
if (config.statuses) {
assert_js.assert.isArray(config.statuses, {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'constructor',
paramName: 'config.statuses'
});
}
if (config.headers) {
assert_js.assert.isType(config.headers, 'object', {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'constructor',
paramName: 'config.headers'
});
}
}
this._statuses = config.statuses;
this._headers = config.headers;
}
/**
* Checks a response to see whether it's cacheable or not, based on this
* object's configuration.
*
* @param {Response} response The response whose cacheability is being
* checked.
* @return {boolean} `true` if the `Response` is cacheable, and `false`
* otherwise.
*/
isResponseCacheable(response) {
{
assert_js.assert.isInstance(response, Response, {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'isResponseCacheable',
paramName: 'response'
});
}
let cacheable = true;
if (this._statuses) {
cacheable = this._statuses.includes(response.status);
}
if (this._headers && cacheable) {
cacheable = Object.keys(this._headers).some(headerName => {
return response.headers.get(headerName) === this._headers[headerName];
});
}
{
if (!cacheable) {
logger_js.logger.groupCollapsed(`The request for ` + `'${getFriendlyURL_js.getFriendlyURL(response.url)}' returned a response that does ` + `not meet the criteria for being cached.`);
logger_js.logger.groupCollapsed(`View cacheability criteria here.`);
logger_js.logger.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));
logger_js.logger.log(`Cacheable headers: ` + JSON.stringify(this._headers, null, 2));
logger_js.logger.groupEnd();
const logFriendlyHeaders = {};
response.headers.forEach((value, key) => {
logFriendlyHeaders[key] = value;
});
logger_js.logger.groupCollapsed(`View response status and headers here.`);
logger_js.logger.log(`Response status: ` + response.status);
logger_js.logger.log(`Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2));
logger_js.logger.groupEnd();
logger_js.logger.groupCollapsed(`View full response details here.`);
logger_js.logger.log(response.headers);
logger_js.logger.log(response);
logger_js.logger.groupEnd();
logger_js.logger.groupEnd();
}
}
return cacheable;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* A class implementing the `cacheWillUpdate` lifecycle callback. This makes it
* easier to add in cacheability checks to requests made via Workbox's built-in
* strategies.
*
* @memberof module:workbox-cacheable-response
*/
class CacheableResponsePlugin {
/**
* To construct a new CacheableResponsePlugin instance you must provide at
* least one of the `config` properties.
*
* If both `statuses` and `headers` are specified, then both conditions must
* be met for the `Response` to be considered cacheable.
*
* @param {Object} config
* @param {Array<number>} [config.statuses] One or more status codes that a
* `Response` can have and be considered cacheable.
* @param {Object<string,string>} [config.headers] A mapping of header names
* and expected values that a `Response` can have and be considered cacheable.
* If multiple headers are provided, only one needs to be present.
*/
constructor(config) {
/**
* @param {Object} options
* @param {Response} options.response
* @return {Response|null}
* @private
*/
this.cacheWillUpdate = async ({
response
}) => {
if (this._cacheableResponse.isResponseCacheable(response)) {
return response;
}
return null;
};
this._cacheableResponse = new CacheableResponse(config);
}
}
exports.CacheableResponse = CacheableResponse;
exports.CacheableResponsePlugin = CacheableResponsePlugin;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-cacheable-response.dev.js.map
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff. Show more