started working on DICOM support (stored in Media object) (#116)
This commit is contained in:
parent
390cea6108
commit
01b6cc3aee
|
@ -77,6 +77,7 @@ Next we'll start the processes described above:
|
|||
|
||||
# In terminal #1, run the following
|
||||
cd frontend
|
||||
yarn install
|
||||
yarn dist -- -c [sandbox|prod]
|
||||
# eg. yarn dist -- -c prod
|
||||
|
||||
|
|
|
@ -537,7 +537,8 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context) ([]*m
|
|||
graph.DFS(g, vertexId, func(relatedVertexId string) bool {
|
||||
relatedResourceFhir, _ := g.Vertex(relatedVertexId)
|
||||
//skip the current resource if it's referenced in this list.
|
||||
if vertexId != resourceVertexId(relatedResourceFhir) {
|
||||
//also skip the current resource if its a Binary resource (which is a special case)
|
||||
if vertexId != resourceVertexId(relatedResourceFhir) && relatedResourceFhir.SourceResourceType != "Binary" {
|
||||
resource.RelatedResourceFhir = append(resource.RelatedResourceFhir, relatedResourceFhir)
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -27,13 +27,19 @@
|
|||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
"src/assets",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "./node_modules/dwv/decoders/",
|
||||
"output": "/assets/dwv/decoders/"
|
||||
},
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/@panva/oauth4webapi/build/index.js"
|
||||
"node_modules/@panva/oauth4webapi/build/index.js",
|
||||
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
|
@ -55,7 +61,7 @@
|
|||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
"maximumError": "10mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"asmcrypto.js": "^2.3.2",
|
||||
"bootstrap": "^4.4.1",
|
||||
"chart.js": "2.9.4",
|
||||
"dwv": "^0.31.0",
|
||||
"fhirpath": "^3.3.0",
|
||||
"humanize-duration": "^3.27.3",
|
||||
"idb": "^7.1.0",
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<button ngbTooltip="scroll layers" type="button" (click)="onChangeTool('Scroll')" class="btn btn-secondary pd-x-25" [class.active]="selectedTool == 'Scroll'"><i class="fas fa-bars"></i></button>
|
||||
<button ngbTooltip="zoom and pan" type="button" (click)="onChangeTool('ZoomAndPan')" class="btn btn-secondary pd-x-25" [class.active]="selectedTool == 'ZoomAndPan'"><i class="fas fa-search"></i></button>
|
||||
<button ngbTooltip="change brightness" type="button" (click)="onChangeTool('WindowLevel')" class="btn btn-secondary pd-x-25" [class.active]="selectedTool == 'WindowLevel'"><i class="fas fa-adjust"></i></button>
|
||||
<button ngbTooltip="add measurements" type="button" (click)="onChangeTool('Draw')" class="btn btn-secondary pd-x-25" [class.active]="selectedTool == 'Draw'"><i class="fas fa-edit"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<button ngbTooltip="toggle orientation" type="button" (click)="toggleOrientation()" class="btn btn-secondary pd-x-25"><i class="fas fa-compress"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" role="group" aria-label="Info">
|
||||
<button ngbTooltip="show info" type="button" (click)="openTagsModal(tagsModal)" class="btn btn-secondary pd-x-25"><i class="fas fa-info"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dwv">
|
||||
<!-- <mat-progress-bar mode="determinate" value="{{ loadProgress }}"></mat-progress-bar>-->
|
||||
<!-- <div class="button-row">-->
|
||||
<!-- <mat-button-toggle-group name="tool" [disabled]="!dataLoaded">-->
|
||||
<!-- <mat-button-toggle value="{{ tool }}" color="primary"-->
|
||||
<!-- *ngFor="let tool of toolNames"-->
|
||||
<!-- title="{{ tool }}"-->
|
||||
<!-- (click)="onChangeTool(tool)"-->
|
||||
<!-- [disabled]="!dataLoaded || !canRunTool(tool)">-->
|
||||
<!-- <mat-icon>{{ getToolIcon(tool) }}</mat-icon>-->
|
||||
<!-- </mat-button-toggle>-->
|
||||
<!-- </mat-button-toggle-group>-->
|
||||
<div id="layerGroup0" class="layerGroup pd-t-10">
|
||||
<div id="dropBox"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #tagsModal let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">DICOM tags</h4>
|
||||
|
||||
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped mg-b-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of visibleMetaData">
|
||||
<th scope="row">{{row.id}}</th>
|
||||
<td>{{row.name}}</td>
|
||||
<td>{{row.value}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<ngb-pagination
|
||||
class="mr-auto"
|
||||
[collectionSize]="metaData.length"
|
||||
[(page)]="tagsPage"
|
||||
[pageSize]="tagsPageSize"
|
||||
(pageChange)="refreshTags()"
|
||||
>
|
||||
</ngb-pagination>
|
||||
<button type="button" class="btn btn-outline-dark" (click)="modal.close('Close click')">Close</button>
|
||||
</div>
|
||||
</ng-template>
|
|
@ -0,0 +1,22 @@
|
|||
/* Layers */
|
||||
//.layerGroup {
|
||||
// position: relative;
|
||||
// padding: 0;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// height: 90%;
|
||||
//}
|
||||
//.layer {
|
||||
// position: absolute;
|
||||
// pointer-events: none;
|
||||
//}
|
||||
|
||||
#dwv {
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
}
|
||||
|
||||
#layerGroup0 {
|
||||
//width: 90%;
|
||||
height: 90%;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DicomComponent } from './dicom.component';
|
||||
|
||||
describe('DicomComponent', () => {
|
||||
let component: DicomComponent;
|
||||
let fixture: ComponentFixture<DicomComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DicomComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DicomComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,262 @@
|
|||
import {Component, Input, OnInit, TemplateRef} from '@angular/core';
|
||||
import * as dwv from 'dwv';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import { VERSION } from '@angular/core';
|
||||
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
||||
// Copied from https://raw.githubusercontent.com/ivmartel/dwv-angular/master/src/app/dwv/dwv.component.ts
|
||||
|
||||
// gui overrides
|
||||
|
||||
// Image decoders (for web workers)
|
||||
dwv.image.decoderScripts = {
|
||||
jpeg2000: 'assets/dwv/decoders/pdfjs/decode-jpeg2000.js',
|
||||
'jpeg-lossless': 'assets/dwv/decoders/rii-mango/decode-jpegloss.js',
|
||||
'jpeg-baseline': 'assets/dwv/decoders/pdfjs/decode-jpegbaseline.js',
|
||||
rle: 'assets/dwv/decoders/dwv/decode-rle.js'
|
||||
};
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'fhir-dicom',
|
||||
templateUrl: './dicom.component.html',
|
||||
styleUrls: ['./dicom.component.scss']
|
||||
})
|
||||
export class DicomComponent implements OnInit {
|
||||
@Input() displayModel: BinaryModel
|
||||
|
||||
public loadProgress = 0;
|
||||
public dataLoaded = false;
|
||||
|
||||
|
||||
public tools = {
|
||||
Scroll: {},
|
||||
ZoomAndPan: {},
|
||||
WindowLevel: {},
|
||||
Draw: {
|
||||
options: ['Ruler']
|
||||
}
|
||||
};
|
||||
selectedTool = 'Scroll';
|
||||
|
||||
private dwvApp: any;
|
||||
private metaData: any[];
|
||||
visibleMetaData: any[] = [] //for pagination usage
|
||||
|
||||
private orientation: string;
|
||||
|
||||
tagsPage = 1;
|
||||
tagsPageSize = 10;
|
||||
// tagsCollectionSize = this.metadata.length;
|
||||
// countries: Country[];
|
||||
|
||||
constructor(private modalService: NgbModal) {}
|
||||
|
||||
ngOnInit() {
|
||||
// create app
|
||||
this.dwvApp = new dwv.App();
|
||||
// initialise app
|
||||
this.dwvApp.init({
|
||||
dataViewConfigs: {'*': [{divId: 'layerGroup0'}]},
|
||||
tools: this.tools
|
||||
});
|
||||
// handle load events
|
||||
let nLoadItem = null;
|
||||
let nReceivedLoadError = null;
|
||||
let nReceivedLoadAbort = null;
|
||||
let isFirstRender = null;
|
||||
this.dwvApp.addEventListener('loadstart', (/*event*/) => {
|
||||
// reset flags
|
||||
this.dataLoaded = false;
|
||||
nLoadItem = 0;
|
||||
nReceivedLoadError = 0;
|
||||
nReceivedLoadAbort = 0;
|
||||
isFirstRender = true;
|
||||
// hide drop box
|
||||
//TODO:
|
||||
// this.showDropbox(false);
|
||||
});
|
||||
this.dwvApp.addEventListener('loadprogress', (event) => {
|
||||
this.loadProgress = event.loaded;
|
||||
});
|
||||
this.dwvApp.addEventListener('renderend', (/*event*/) => {
|
||||
if (isFirstRender) {
|
||||
isFirstRender = false;
|
||||
// available tools
|
||||
let selectedTool = 'ZoomAndPan';
|
||||
if (this.dwvApp.canScroll()) {
|
||||
selectedTool = 'Scroll';
|
||||
}
|
||||
this.onChangeTool(selectedTool);
|
||||
}
|
||||
});
|
||||
this.dwvApp.addEventListener('load', (/*event*/) => {
|
||||
// set dicom tags
|
||||
this.metaData = dwv.utils.objectToArray(this.dwvApp.getMetaData(0));
|
||||
// set data loaded flag
|
||||
this.dataLoaded = true;
|
||||
});
|
||||
this.dwvApp.addEventListener('loadend', (/*event*/) => {
|
||||
if (nReceivedLoadError) {
|
||||
this.loadProgress = 0;
|
||||
alert('Received errors during load. Check log for details.');
|
||||
// show drop box if nothing has been loaded
|
||||
if (!nLoadItem) {
|
||||
//TODO: this.showDropbox(true);
|
||||
}
|
||||
}
|
||||
if (nReceivedLoadAbort) {
|
||||
this.loadProgress = 0;
|
||||
alert('Load was aborted.');
|
||||
//TODO: this.showDropbox(true);
|
||||
}
|
||||
});
|
||||
this.dwvApp.addEventListener('loaditem', (/*event*/) => {
|
||||
++nLoadItem;
|
||||
});
|
||||
this.dwvApp.addEventListener('loaderror', (event) => {
|
||||
console.error(event.error);
|
||||
++nReceivedLoadError;
|
||||
});
|
||||
this.dwvApp.addEventListener('loadabort', (/*event*/) => {
|
||||
++nReceivedLoadAbort;
|
||||
});
|
||||
|
||||
// handle key events
|
||||
this.dwvApp.addEventListener('keydown', (event) => {
|
||||
this.dwvApp.defaultOnKeydown(event);
|
||||
});
|
||||
// handle window resize
|
||||
window.addEventListener('resize', this.dwvApp.onResize);
|
||||
|
||||
// setup drop box
|
||||
//TODO: this.setupDropbox();
|
||||
|
||||
// possible load from location
|
||||
// dwv.utils.loadFromUri(window.location.href, this.dwvApp);
|
||||
if(!this.displayModel) {
|
||||
return;
|
||||
}
|
||||
//Load from Input file
|
||||
let files = [new File([
|
||||
this.dataBase64toBlob(this.displayModel.data, "application/dicom")
|
||||
], "dicom.dcm", {type: "application/dicom"})]
|
||||
|
||||
this.dwvApp.loadFiles(files);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change tool event.
|
||||
* @param tool The new tool name.
|
||||
*/
|
||||
onChangeTool = (tool: string) => {
|
||||
if ( this.dwvApp ) {
|
||||
this.selectedTool = tool;
|
||||
this.dwvApp.setTool(tool);
|
||||
if (tool === 'Draw') {
|
||||
this.onChangeShape(this.tools.Draw.options[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool can be run.
|
||||
*
|
||||
* @param tool The tool name.
|
||||
* @returns True if the tool can be run.
|
||||
*/
|
||||
canRunTool = (tool: string) => {
|
||||
let res: boolean;
|
||||
if (tool === 'Scroll') {
|
||||
res = this.dwvApp.canScroll();
|
||||
} else if (tool === 'WindowLevel') {
|
||||
res = this.dwvApp.canWindowLevel();
|
||||
} else {
|
||||
res = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toogle the viewer orientation.
|
||||
*/
|
||||
toggleOrientation = () => {
|
||||
if (typeof this.orientation !== 'undefined') {
|
||||
if (this.orientation === 'axial') {
|
||||
this.orientation = 'coronal';
|
||||
} else if (this.orientation === 'coronal') {
|
||||
this.orientation = 'sagittal';
|
||||
} else if (this.orientation === 'sagittal') {
|
||||
this.orientation = 'axial';
|
||||
}
|
||||
} else {
|
||||
// default is most probably axial
|
||||
this.orientation = 'coronal';
|
||||
}
|
||||
// update data view config
|
||||
const config = {
|
||||
'*': [
|
||||
{
|
||||
divId: 'layerGroup0',
|
||||
orientation: this.orientation
|
||||
}
|
||||
]
|
||||
};
|
||||
this.dwvApp.setDataViewConfig(config);
|
||||
// render data
|
||||
for (let i = 0; i < this.dwvApp.getNumberOfLoadedData(); ++i) {
|
||||
this.dwvApp.render(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change draw shape event.
|
||||
* @param shape The new shape name.
|
||||
*/
|
||||
private onChangeShape = (shape: string) => {
|
||||
if ( this.dwvApp && this.selectedTool === 'Draw') {
|
||||
this.dwvApp.setToolFeatures({shapeName: shape});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a reset event.
|
||||
*/
|
||||
onReset = () => {
|
||||
if ( this.dwvApp ) {
|
||||
this.dwvApp.resetDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the DICOM tags dialog.
|
||||
*/
|
||||
openTagsModal(template: TemplateRef<any>) {
|
||||
this.refreshTags();
|
||||
this.modalService.open(template, { size: 'lg', ariaLabelledBy: 'modal-basic-title' });
|
||||
}
|
||||
|
||||
refreshTags() {
|
||||
//TODO: if tag.value is a array, content should be json encoded
|
||||
this.visibleMetaData = this.metaData.map((tag, i) => ({ id: i + 1, ...tag })).slice(
|
||||
(this.tagsPage - 1) * this.tagsPageSize,
|
||||
(this.tagsPage - 1) * this.tagsPageSize + this.tagsPageSize,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//This function is used to convert base64 binary data to a blob
|
||||
dataBase64toBlob(base64Data, contentType) {
|
||||
// convert base64 to raw binary data held in a string
|
||||
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
|
||||
var byteString = atob(base64Data);
|
||||
|
||||
// write the bytes of the string to an ArrayBuffer
|
||||
var ab = new ArrayBuffer(byteString.length);
|
||||
var ia = new Uint8Array(ab);
|
||||
for (var i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([ab], { type: contentType });
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import {ProcedureComponent} from '../resources/procedure/procedure.component';
|
|||
import {DiagnosticReportComponent} from '../resources/diagnostic-report/diagnostic-report.component';
|
||||
import {PractitionerComponent} from '../resources/practitioner/practitioner.component';
|
||||
import {DocumentReferenceComponent} from '../resources/document-reference/document-reference.component';
|
||||
import {MediaComponent} from '../resources/media/media.component';
|
||||
|
||||
@Component({
|
||||
selector: 'fhir-resource',
|
||||
|
@ -118,6 +119,9 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
|||
case "Immunization": {
|
||||
return ImmunizationComponent;
|
||||
}
|
||||
case "Media": {
|
||||
return MediaComponent;
|
||||
}
|
||||
case "Medication": {
|
||||
return MedicationComponent;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
<ng-container [ngTemplateOutlet]="
|
||||
<div *ngIf="loading">
|
||||
<div class="text-center mg-b-20">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<ng-container *ngIf="!loading" [ngTemplateOutlet]="
|
||||
displayModel?.content_type == 'application/pdf' ? showPdf :
|
||||
displayModel?.content_type == 'text/markdown' ? showMarkdown :
|
||||
displayModel?.content_type == 'text/plain' ? showText :
|
||||
displayModel?.content_type == 'application/dicom' ? showDicom :
|
||||
(displayModel?.content_type == 'text/html' || displayModel?.content_type == 'application/html') ? showHtml :
|
||||
(displayModel?.content_type == 'application/xml' || displayModel?.content_type == 'application/json') ? showHighlight :
|
||||
(displayModel?.content_type == 'image/jpeg' || displayModel?.content_type == 'image/png') ? showImg :
|
||||
|
@ -26,6 +37,9 @@
|
|||
<ng-template #showText>
|
||||
<fhir-binary-text [displayModel]="displayModel"></fhir-binary-text>
|
||||
</ng-template>
|
||||
<ng-template #showDicom>
|
||||
<fhir-dicom [displayModel]="displayModel"></fhir-dicom>
|
||||
</ng-template>
|
||||
<ng-template #showEmpty>
|
||||
Unknown Binary content type {{displayModel?.content_type}}
|
||||
</ng-template>
|
||||
|
|
|
@ -2,16 +2,24 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
|
||||
import { BinaryComponent } from './binary.component';
|
||||
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {FastenApiService} from '../../../../services/fasten-api.service';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
describe('BinaryComponent', () => {
|
||||
let component: BinaryComponent;
|
||||
let fixture: ComponentFixture<BinaryComponent>;
|
||||
let mockedFastenApiService
|
||||
|
||||
beforeEach(async () => {
|
||||
mockedFastenApiService = jasmine.createSpyObj('FastenApiService', ['getBinaryModel'])
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BinaryComponent ],
|
||||
imports: [NgbCollapseModule]
|
||||
|
||||
imports: [NgbCollapseModule, RouterTestingModule],
|
||||
providers: [{
|
||||
provide: FastenApiService,
|
||||
useValue: mockedFastenApiService
|
||||
}]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
|||
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
||||
import {Router} from '@angular/router';
|
||||
import {AttachmentModel} from '../../../../../lib/models/datatypes/attachment-model';
|
||||
import {FastenApiService} from '../../../../services/fasten-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fhir-binary',
|
||||
|
@ -11,13 +13,27 @@ import {Router} from '@angular/router';
|
|||
export class BinaryComponent implements OnInit, FhirResourceComponentInterface {
|
||||
@Input() displayModel: BinaryModel
|
||||
@Input() showDetails: boolean = true
|
||||
@Input() attachmentSourceId: string
|
||||
@Input() attachmentModel: AttachmentModel //can only have attachmentModel or binaryModel, not both.
|
||||
|
||||
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
||||
loading: boolean = false
|
||||
constructor(public changeRef: ChangeDetectorRef, public router: Router, public fastenApi: FastenApiService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if(!this.displayModel && this.attachmentSourceId && this.attachmentModel){
|
||||
this.loading = true
|
||||
this.fastenApi.getBinaryModel(this.attachmentSourceId, this.attachmentModel)
|
||||
.subscribe((binaryModel: BinaryModel) => {
|
||||
this.loading = false
|
||||
this.displayModel = binaryModel
|
||||
this.markForCheck()
|
||||
}, (error) => {
|
||||
this.loading = false
|
||||
this.markForCheck()
|
||||
})
|
||||
}
|
||||
}
|
||||
markForCheck(){
|
||||
this.changeRef.markForCheck()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<fhir-ui-table [displayModel]="displayModel" [tableData]="tableData"></fhir-ui-table>
|
||||
|
||||
<div *ngIf="!showDetails">
|
||||
<fhir-binary *ngFor="let binaryModel of displayModel.content" [displayModel]="binaryModel"></fhir-binary>
|
||||
<fhir-binary *ngFor="let attachmentModel of displayModel.content" [attachmentModel]="attachmentModel" [attachmentSourceId]="displayModel?.source_id" ></fhir-binary>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showDetails" class="card-footer">
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<div class="card card-fhir-resource" >
|
||||
<div class="card-header" (click)="isCollapsed = ! isCollapsed">
|
||||
<div>
|
||||
<h6 class="card-title">{{displayModel?.sort_title}}</h6>
|
||||
<!-- <p class="card-text tx-gray-400" *ngIf="displayModel?.created_at"><strong>Created at</strong> {{displayModel?.created_at | date}}</p>-->
|
||||
</div>
|
||||
<fhir-ui-badge class="float-right" [status]="displayModel?.status">{{displayModel?.status}}</fhir-ui-badge>
|
||||
<!-- <div class="btn-group">-->
|
||||
<!-- <button class="btn active">Day</button>-->
|
||||
<!-- <button class="btn">Week</button>-->
|
||||
<!-- <button class="btn">Month</button>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" class="card-body">
|
||||
<p class="az-content-text mg-b-20">An action that is or was performed on or for a patient, practitioner, device, organization, or location. For example, this can be a physical intervention on a patient like an operation, or less invasive like long term services, counseling, or hypnotherapy.</p>
|
||||
<fhir-ui-table [displayModel]="displayModel" [tableData]="tableData"></fhir-ui-table>
|
||||
|
||||
<div *ngIf="!showDetails">
|
||||
<fhir-binary [attachmentModel]="displayModel.content" [attachmentSourceId]="displayModel?.source_id"></fhir-binary>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showDetails" class="card-footer">
|
||||
<a class="float-right" routerLink="/source/{{displayModel?.source_id}}/resource/{{displayModel?.source_resource_id}}">details</a>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MediaComponent } from './media.component';
|
||||
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
describe('MediaComponent', () => {
|
||||
let component: MediaComponent;
|
||||
let fixture: ComponentFixture<MediaComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ MediaComponent ],
|
||||
imports: [NgbCollapseModule, RouterTestingModule],
|
||||
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MediaComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
import {DocumentReferenceModel} from '../../../../../lib/models/resources/document-reference-model';
|
||||
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
||||
import {Router} from '@angular/router';
|
||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
||||
import {MediaModel} from '../../../../../lib/models/resources/media-model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-media',
|
||||
templateUrl: './media.component.html',
|
||||
styleUrls: ['./media.component.scss']
|
||||
})
|
||||
export class MediaComponent implements OnInit, FhirResourceComponentInterface{
|
||||
@Input() displayModel: MediaModel
|
||||
@Input() showDetails: boolean = true
|
||||
isCollapsed: boolean = false
|
||||
tableData: TableRowItem[] = []
|
||||
|
||||
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.tableData = [
|
||||
{
|
||||
label: 'Description',
|
||||
data: this.displayModel?.description,
|
||||
enabled: !!this.displayModel?.description,
|
||||
},
|
||||
// {
|
||||
// label: 'Performer',
|
||||
// data: this.displayModel?.performer,
|
||||
// data_type: TableRowItemDataType.Reference,
|
||||
// enabled: this.displayModel?.has_performer,
|
||||
// },
|
||||
// {
|
||||
// label: 'Conclusion',
|
||||
// data: this.displayModel?.conclusion,
|
||||
// enabled: !!this.displayModel?.conclusion,
|
||||
// },
|
||||
];
|
||||
}
|
||||
markForCheck(){
|
||||
this.changeRef.markForCheck()
|
||||
}
|
||||
}
|
|
@ -1,16 +1,30 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GlossaryLookupComponent } from './glossary-lookup.component';
|
||||
import {FastenApiService} from '../../services/fasten-api.service';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
describe('GlossaryLookupComponent', () => {
|
||||
let component: GlossaryLookupComponent;
|
||||
let fixture: ComponentFixture<GlossaryLookupComponent>;
|
||||
let mockedFastenApiService
|
||||
|
||||
beforeEach(async () => {
|
||||
mockedFastenApiService = jasmine.createSpyObj('FastenApiService', ['getGlossarySearchByCode'])
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ GlossaryLookupComponent ]
|
||||
declarations: [ GlossaryLookupComponent ],
|
||||
providers: [{
|
||||
provide: FastenApiService,
|
||||
useValue: mockedFastenApiService
|
||||
}]
|
||||
})
|
||||
.compileComponents();
|
||||
mockedFastenApiService.getGlossarySearchByCode.and.returnValue(of({
|
||||
url: 'http://www.example.com',
|
||||
publisher: 'test-publisher',
|
||||
description: 'test description'
|
||||
}));
|
||||
|
||||
fixture = TestBed.createComponent(GlossaryLookupComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
|
|
@ -65,6 +65,8 @@ import { PractitionerComponent } from './fhir/resources/practitioner/practitione
|
|||
import {PipesModule} from '../pipes/pipes.module';
|
||||
import { NlmTypeaheadComponent } from './nlm-typeahead/nlm-typeahead.component';
|
||||
import { DocumentReferenceComponent } from './fhir/resources/document-reference/document-reference.component';
|
||||
import { DicomComponent } from './fhir/datatypes/dicom/dicom.component';
|
||||
import { MediaComponent } from './fhir/resources/media/media.component';
|
||||
import { GlossaryLookupComponent } from './glossary-lookup/glossary-lookup.component';
|
||||
|
||||
@NgModule({
|
||||
|
@ -140,6 +142,8 @@ import { GlossaryLookupComponent } from './glossary-lookup/glossary-lookup.compo
|
|||
NlmTypeaheadComponent,
|
||||
DocumentReferenceComponent,
|
||||
GlossaryLookupComponent,
|
||||
DicomComponent,
|
||||
MediaComponent,
|
||||
],
|
||||
exports: [
|
||||
ComponentsSidebarComponent,
|
||||
|
|
|
@ -12,13 +12,13 @@ import {
|
|||
BundleEntry,
|
||||
Bundle,
|
||||
Organization,
|
||||
Practitioner, MedicationRequest, Patient, Encounter, DocumentReference, Media, DiagnosticReport, Reference
|
||||
Practitioner, MedicationRequest, Patient, Encounter, DocumentReference, Media, DiagnosticReport, Reference, Binary
|
||||
} from 'fhir/r4';
|
||||
import {uuidV4} from '../../../lib/utils/uuid';
|
||||
|
||||
interface ResourceStorage {
|
||||
[resourceType: string]: {
|
||||
[resourceId: string]: Condition | Patient | MedicationRequest | Organization | FhirLocation | Practitioner | Procedure | Encounter | DocumentReference | Media | DiagnosticReport
|
||||
[resourceId: string]: Condition | Patient | MedicationRequest | Organization | FhirLocation | Practitioner | Procedure | Encounter | DocumentReference | Media | DiagnosticReport | Binary
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,6 +416,15 @@ function resourceCreateMedicationToR4MedicationRequest(resourceStorage: Resource
|
|||
}
|
||||
|
||||
function resourceAttachmentToR4DocumentReference(resourceStorage: ResourceStorage, resourceAttachment: ResourceCreateAttachment): ResourceStorage {
|
||||
resourceStorage['Binary'] = resourceStorage['Binary'] || {}
|
||||
let binaryResource = {
|
||||
id: uuidV4(),
|
||||
resourceType: 'Binary',
|
||||
contentType: resourceAttachment.file_type,
|
||||
data: resourceAttachment.file_content,
|
||||
} as Binary
|
||||
resourceStorage['Binary'][binaryResource.id] = binaryResource
|
||||
|
||||
|
||||
resourceStorage['DocumentReference'] = resourceStorage['DocumentReference'] || {}
|
||||
|
||||
|
@ -437,7 +446,7 @@ function resourceAttachmentToR4DocumentReference(resourceStorage: ResourceStorag
|
|||
{
|
||||
attachment: {
|
||||
contentType: resourceAttachment.file_type,
|
||||
data: resourceAttachment.file_content,
|
||||
url: `urn:uuid:${binaryResource.id}`, //Binary
|
||||
title: resourceAttachment.name,
|
||||
}
|
||||
}
|
||||
|
@ -455,12 +464,19 @@ function resourceAttachmentToR4DocumentReference(resourceStorage: ResourceStorag
|
|||
} as DocumentReference
|
||||
resourceStorage['DocumentReference'][documentReferenceResource.id] = documentReferenceResource
|
||||
|
||||
//TODO create Binary object?
|
||||
|
||||
return resourceStorage
|
||||
}
|
||||
|
||||
function resourceAttachmentToR4DiagnosticReport(resourceStorage: ResourceStorage, resourceAttachment: ResourceCreateAttachment): ResourceStorage {
|
||||
resourceStorage['Binary'] = resourceStorage['Binary'] || {}
|
||||
let binaryResource = {
|
||||
id: uuidV4(),
|
||||
resourceType: 'Binary',
|
||||
contentType: resourceAttachment.file_type,
|
||||
data: resourceAttachment.file_content,
|
||||
} as Binary
|
||||
resourceStorage['Binary'][binaryResource.id] = binaryResource
|
||||
|
||||
resourceStorage['Media'] = resourceStorage['Media'] || {}
|
||||
|
||||
let mediaResource = {
|
||||
|
@ -476,7 +492,7 @@ function resourceAttachmentToR4DiagnosticReport(resourceStorage: ResourceStorage
|
|||
},
|
||||
content: {
|
||||
contentType: resourceAttachment.file_type,
|
||||
data: resourceAttachment.file_content,
|
||||
url: `urn:uuid:${binaryResource.id}`, //Binary,
|
||||
title: resourceAttachment.name,
|
||||
},
|
||||
} as Media
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {ResponseWrapper} from '../models/response-wrapper';
|
||||
|
@ -15,6 +15,8 @@ import {GetEndpointAbsolutePath} from '../../lib/utils/endpoint_absolute_path';
|
|||
import {environment} from '../../environments/environment';
|
||||
import {ResourceAssociation} from '../models/fasten/resource_association';
|
||||
import {ValueSet} from 'fhir/r4';
|
||||
import {AttachmentModel} from '../../lib/models/datatypes/attachment-model';
|
||||
import {BinaryModel} from '../../lib/models/resources/binary-model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -180,4 +182,35 @@ export class FastenApiService {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
getBinaryModel(sourceId: string, attachmentModel: AttachmentModel): Observable<BinaryModel> {
|
||||
if(attachmentModel.url && !attachmentModel.data){
|
||||
//this attachment model is a refernce to a Binary model, we need to download it first.
|
||||
|
||||
let binaryResourceId = attachmentModel.url
|
||||
|
||||
//strip out the urn prefix (if this is an embedded id, eg. urn:uuid:2a35e080-c5f7-4dde-b0cf-8210505708f1)
|
||||
let urnPrefix = "urn:uuid:";
|
||||
if (binaryResourceId.startsWith(urnPrefix)) {
|
||||
// PREFIX is exactly at the beginning
|
||||
binaryResourceId = binaryResourceId.slice(urnPrefix.length);
|
||||
}
|
||||
|
||||
//TODO: this is a naiive solution.
|
||||
//assumes that this is a relative or absolutie url in the following format:
|
||||
// 'Binary/xxx-xxx-xxx-xxx'
|
||||
// 'https://www.example.com/R4/path/Binary/xxx-xx-x-xx'
|
||||
let urlParts = binaryResourceId.split("Binary/");
|
||||
binaryResourceId = urlParts[urlParts.length - 1];
|
||||
|
||||
return this.getResourceBySourceId(sourceId, binaryResourceId).pipe(
|
||||
map((resourceFhir: ResourceFhir) => {
|
||||
return new BinaryModel(resourceFhir.resource_raw)
|
||||
})
|
||||
)
|
||||
} else {
|
||||
return of(new BinaryModel(attachmentModel));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export enum ResourceType {
|
|||
Goal = "Goal",
|
||||
Immunization = "Immunization",
|
||||
Location = "Location",
|
||||
Media = "Media",
|
||||
Medication = "Medication",
|
||||
MedicationDispense = "MedicationDispense",
|
||||
MedicationRequest = "MedicationRequest",
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import fhirpath from 'fhirpath';
|
||||
|
||||
export class AttachmentModel {
|
||||
contentType: string
|
||||
language: string
|
||||
data: string
|
||||
url: string
|
||||
size: number
|
||||
hash: string
|
||||
title: string
|
||||
creation: string
|
||||
|
||||
constructor(fhirData: any) {
|
||||
this.contentType = fhirData.contentType
|
||||
this.language = fhirData.language
|
||||
this.data = fhirData.data
|
||||
this.url = fhirData.url
|
||||
this.size = fhirData.size
|
||||
this.hash = fhirData.hash
|
||||
this.title = fhirData.title
|
||||
this.creation = fhirData.creation
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import {FastenOptions} from './fasten/fasten-options';
|
|||
import {FastenDisplayModel} from './fasten/fasten-display-model';
|
||||
import {MedicationRequestModel} from './resources/medication-request-model';
|
||||
import {BinaryModel} from './resources/binary-model';
|
||||
import {MediaModel} from './resources/media-model';
|
||||
|
||||
// import {BinaryModel} from './resources/binary-model';
|
||||
|
||||
|
@ -93,6 +94,9 @@ export function fhirModelFactory(modelResourceType: ResourceType, fhirResourceWr
|
|||
case "Location":
|
||||
resourceModel = new LocationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
|
||||
break
|
||||
case "Media":
|
||||
resourceModel = new MediaModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
|
||||
break
|
||||
case "Medication":
|
||||
resourceModel = new MedicationModel(fhirResourceWrapper.resource_raw, fhirVersion, fastenOptions)
|
||||
break
|
||||
|
|
|
@ -16,6 +16,8 @@ describe('ConditionModel', () => {
|
|||
it('should parse example1.json', () => {
|
||||
// let fixture = require("../../fixtures/r4/resources/condition/example1.json")
|
||||
let expected = new ConditionModel({})
|
||||
expected.code_id = '39065001'
|
||||
expected.code_system = 'http://snomed.info/sct'
|
||||
expected.code_text = 'Burn of ear'
|
||||
expected.severity_text = 'Severe'
|
||||
// expected.hasAsserter: boolean | undefined
|
||||
|
@ -55,6 +57,8 @@ describe('ConditionModel', () => {
|
|||
it('should parse example3.json', () => {
|
||||
let expected = new ConditionModel({})
|
||||
expected.code_text = 'Fever'
|
||||
expected.code_id = '386661006'
|
||||
expected.code_system = 'http://snomed.info/sct'
|
||||
expected.severity_text = 'Mild'
|
||||
expected.has_asserter = true
|
||||
expected.asserter = { reference: 'Practitioner/f201' }
|
||||
|
|
|
@ -2,7 +2,7 @@ import { DocumentReferenceModel } from './document-reference-model';
|
|||
import {AdverseEventModel} from './adverse-event-model';
|
||||
import {CodableConceptModel} from '../datatypes/codable-concept-model';
|
||||
import * as example1Fixture from "../../fixtures/r4/resources/documentReference/example1.json"
|
||||
import {BinaryModel} from './binary-model';
|
||||
import {AttachmentModel} from '../datatypes/attachment-model';
|
||||
|
||||
|
||||
describe('DocumentReferenceModel', () => {
|
||||
|
@ -29,7 +29,7 @@ describe('DocumentReferenceModel', () => {
|
|||
]
|
||||
})
|
||||
expected.content = [
|
||||
new BinaryModel({
|
||||
new AttachmentModel({
|
||||
"contentType": "application/hl7-v3+xml",
|
||||
"language": "en-US",
|
||||
"url": "http://example.org/xds/mhd/Binary/07a6483f-732b-461e-86b6-edb665c45510",
|
||||
|
|
|
@ -7,6 +7,7 @@ import {FastenDisplayModel} from '../fasten/fasten-display-model';
|
|||
import {FastenOptions} from '../fasten/fasten-options';
|
||||
import {Attachment} from 'fhir/r4';
|
||||
import {BinaryModel} from './binary-model';
|
||||
import {AttachmentModel} from '../datatypes/attachment-model';
|
||||
|
||||
export class DocumentReferenceModel extends FastenDisplayModel {
|
||||
|
||||
|
@ -18,7 +19,7 @@ export class DocumentReferenceModel extends FastenDisplayModel {
|
|||
class_coding: CodingModel | undefined
|
||||
created_at: string | undefined
|
||||
security_label_coding: CodingModel | undefined
|
||||
content: BinaryModel[] | undefined
|
||||
content: AttachmentModel[] | undefined
|
||||
context: {
|
||||
eventCoding: CodingModel
|
||||
facilityTypeCoding: CodingModel
|
||||
|
@ -81,8 +82,8 @@ export class DocumentReferenceModel extends FastenDisplayModel {
|
|||
this.category = new CodableConceptModel(_.get(fhirResource, 'category[0]') || {});
|
||||
this.content = _.get(fhirResource, 'content', []).map((content: any) => {
|
||||
const attachment: Attachment = _.get(content, 'attachment');
|
||||
const binaryModel = new BinaryModel(attachment, fhirVersion);
|
||||
return binaryModel;
|
||||
const attachmentModel = new AttachmentModel(attachment);
|
||||
return attachmentModel;
|
||||
})
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import {FastenDisplayModel} from '../fasten/fasten-display-model';
|
||||
import {CodableConceptModel} from '../datatypes/codable-concept-model';
|
||||
import {ReferenceModel} from '../datatypes/reference-model';
|
||||
import {fhirVersions, ResourceType} from '../constants';
|
||||
import {FastenOptions} from '../fasten/fasten-options';
|
||||
import * as _ from "lodash";
|
||||
import {AttachmentModel} from '../datatypes/attachment-model';
|
||||
|
||||
|
||||
export class MediaModel extends FastenDisplayModel {
|
||||
status: string
|
||||
type: CodableConceptModel
|
||||
reasonCode: CodableConceptModel[]
|
||||
deviceName: string
|
||||
device: ReferenceModel
|
||||
height: number
|
||||
width: number
|
||||
description: string
|
||||
content: AttachmentModel
|
||||
|
||||
|
||||
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
|
||||
super(fastenOptions)
|
||||
this.source_resource_type = ResourceType.Media
|
||||
|
||||
this.status = _.get(fhirResource, 'status');
|
||||
this.type = new CodableConceptModel(_.get(fhirResource, 'type'));
|
||||
this.reasonCode = (_.get(fhirResource, 'reasonCode') || []).map((_reasonCode: any) => new CodableConceptModel(_reasonCode));
|
||||
this.deviceName = _.get(fhirResource, 'deviceName');
|
||||
this.device = _.get(fhirResource, 'device');
|
||||
this.height = _.get(fhirResource, 'height');
|
||||
this.width = _.get(fhirResource, 'width');
|
||||
this.description = _.get(fhirResource, 'note');
|
||||
this.content = new AttachmentModel(_.get(fhirResource, 'content'));
|
||||
}
|
||||
}
|
|
@ -3273,6 +3273,15 @@ domutils@^2.8.0:
|
|||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
dwv@^0.31.0:
|
||||
version "0.31.0"
|
||||
resolved "https://registry.npmjs.org/dwv/-/dwv-0.31.0.tgz#c9777291774d49225ba0c1b8020d104f09312ebf"
|
||||
integrity sha512-c/9cmBdPHtH2l2YEacMLdkqpzXTx0rfcof70ujvR2XhDC/IVgGuXUkM1+ZJKvfFyQowditTciif938ib3SB9vQ==
|
||||
dependencies:
|
||||
jszip "~3.7.0"
|
||||
konva "~8.3.0"
|
||||
magic-wand-tool "~1.1.7"
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
|
@ -4743,6 +4752,16 @@ jszip@^3.1.3:
|
|||
readable-stream "~2.3.6"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
jszip@~3.7.0:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9"
|
||||
integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==
|
||||
dependencies:
|
||||
lie "~3.3.0"
|
||||
pako "~1.0.2"
|
||||
readable-stream "~2.3.6"
|
||||
set-immediate-shim "~1.0.1"
|
||||
|
||||
karma-chrome-launcher@~3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz#baca9cc071b1562a1db241827257bfe5cab597ea"
|
||||
|
@ -4832,6 +4851,11 @@ klona@^2.0.4, klona@^2.0.5:
|
|||
resolved "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
|
||||
integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
|
||||
|
||||
konva@~8.3.0:
|
||||
version "8.3.14"
|
||||
resolved "https://registry.npmjs.org/konva/-/konva-8.3.14.tgz#691ecb6f4568c58818359af369f03e7438ea3640"
|
||||
integrity sha512-6I/TZppgY3Frs//AvZ87YVQLFxLywitb8wLS3qMM+Ih9e4QcB5Yy8br6eq7DdUzxPdbsYTz1FQBHzNxs08M1Tw==
|
||||
|
||||
less-loader@11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz#a31b2bc5cdfb62f1c7de9b2d01cd944c22b1a024"
|
||||
|
@ -4973,6 +4997,11 @@ magic-string@^0.26.0:
|
|||
dependencies:
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
magic-wand-tool@~1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.npmjs.org/magic-wand-tool/-/magic-wand-tool-1.1.7.tgz#c1d49c5f26307bec06eb8c6ac51e03fff5b9d61b"
|
||||
integrity sha512-S4rHzCs/bAp7nhQGKeg+McWuqrdyZKpnu8Ahd8AU7NzuLTm/Hh8tkpv1tW91Kmm59foIrXzip1d+P9NDoyxrZA==
|
||||
|
||||
make-dir@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||
|
@ -6698,6 +6727,11 @@ set-blocking@^2.0.0:
|
|||
resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
|
||||
|
||||
set-immediate-shim@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
|
||||
integrity sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
|
|
4
go.mod
4
go.mod
|
@ -5,7 +5,8 @@ go 1.18
|
|||
require (
|
||||
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b
|
||||
github.com/dominikbraun/graph v0.15.0
|
||||
github.com/fastenhealth/fasten-sources v0.1.2
|
||||
github.com/fastenhealth/fasten-sources v0.1.7
|
||||
github.com/fastenhealth/gofhir-models v0.0.4
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/glebarez/sqlite v1.5.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
|
@ -25,7 +26,6 @@ require (
|
|||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fastenhealth/gofhir-models v0.0.4 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -74,8 +74,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fastenhealth/fasten-sources v0.1.2 h1:VW+gNggUeX+SrqYsWleaiYlCRNItpu4F3Bml1g5UVWQ=
|
||||
github.com/fastenhealth/fasten-sources v0.1.2/go.mod h1:OYNf47aUBNP2J0kPkrm1X9Mq+30v92yDBrRy4nqPNEU=
|
||||
github.com/fastenhealth/fasten-sources v0.1.7 h1:KcLNfsCd/ujldAXHsbJErxXfM+llMv8r9D+o53pP9kg=
|
||||
github.com/fastenhealth/fasten-sources v0.1.7/go.mod h1:OYNf47aUBNP2J0kPkrm1X9Mq+30v92yDBrRy4nqPNEU=
|
||||
github.com/fastenhealth/gofhir-models v0.0.4 h1:Q//StwNXGfK+WAS2DckGBHAP1R4cHMRZEF/sLGgmR04=
|
||||
github.com/fastenhealth/gofhir-models v0.0.4/go.mod h1:xB8ikGxu3bUq2b1JYV+CZpHqBaLXpOizFR0eFBCunis=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
|
|
Loading…
Reference in New Issue