@@ -9,6 +9,7 @@ | |||
"js-uuid": "0.0.6", | |||
"js-yaml": "^3.13.1", | |||
"linkstate": "^1.1.1", | |||
"papaya-viewer": "^1.0.1449", | |||
"popper.js": "^1.16.1", | |||
"preact": "^8.2.9", | |||
"preact-router": "^2.6.1", | |||
@@ -51,6 +51,8 @@ export default { | |||
'node_modules/streamsaver/sw.js': 'dist/sw.js', | |||
'src/js/thirdparty/StreamSaver.js': 'dist/js/StreamSaver.js', | |||
'node_modules/web-streams-polyfill/dist/ponyfill.js': 'dist/js/web-streams-polyfill/ponyfill.js', | |||
'node_modules/papaya-viewer/release/current/standard/papaya.js': 'dist/js/papaya.js', | |||
'node_modules/papaya-viewer/release/current/standard/papaya.css': 'dist/css/papaya.css', | |||
verbose: true | |||
}), | |||
buble({jsx: 'h'}), | |||
@@ -4,6 +4,7 @@ | |||
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" /> | |||
<link rel="stylesheet" type="text/css" href="/css/all.min.css" /> | |||
<link rel="stylesheet" type="text/css" href="/css/index.css" /> | |||
<link rel="stylesheet" type="text/css" href="/css/papaya.css" /> | |||
<script language="javascript" src="/js/web-streams-polyfill/ponyfill.js"></script> | |||
<script language="javascript"> | |||
window.process = { 'env': { 'NODE_ENV': 'production' } }; | |||
@@ -138,6 +138,20 @@ class WBCollectionContent extends Component { | |||
onclick={ () => { | |||
alert('Not implemented.') | |||
} }><i class="far fa-eye"></i></button> | |||
{ item[1].toLowerCase().endsWith('.nii') ? ( | |||
<button class="btn btn-outline-primary mx-1" title="View Image" | |||
onclick={ () => manifestWorker.postMessage([ 'getFile', | |||
'.' + collectionPath + '/' + item[1] ]).then(e => { | |||
const file = e.data[1]; | |||
const blob = new Blob([ | |||
JSON.stringify({ 'name': item[1], 'file': file }) | |||
]); | |||
const blocksBlobUrl = URL.createObjectURL(blob); | |||
window.open('/image-viewer/' + encodeURIComponent(blocksBlobUrl), '_blank'); | |||
}) }><i class="far fa-eye"></i></button> | |||
) : null } | |||
</div> | |||
) : null) | |||
] | |||
@@ -12,6 +12,7 @@ import WBUsersPage from 'wb-users-page'; | |||
import WBWorkflowView from 'wb-workflow-view'; | |||
import WBLaunchWorkflowPage from 'wb-launch-workflow-page'; | |||
import WBDownloadPage from 'wb-download-page'; | |||
import WBImageViewerPage from 'wb-image-viewer-page'; | |||
import arvadosTypeName from 'arvados-type-name'; | |||
class WBApp extends Component { | |||
@@ -94,6 +95,8 @@ class WBApp extends Component { | |||
<WBLaunchWorkflowPage path="/workflow-launch/:workflowUuid" app={ this } /> | |||
<WBDownloadPage path="/download/:blocksBlobUrl/:inline?" app={ this } /> | |||
<WBImageViewerPage path="/image-viewer/:blobUrl" app={ this } /> | |||
</Router> | |||
); | |||
} | |||
@@ -0,0 +1,103 @@ | |||
import { h, Component } from 'preact'; | |||
import makeArvadosRequest from 'make-arvados-request'; | |||
function downloadFile(arvHost, arvToken, file) { | |||
const blockRefs = file[0]; | |||
let prom = makeArvadosRequest(arvHost, arvToken, | |||
'/arvados/v1/keep_services'); | |||
let proxy; | |||
prom = prom.then(xhr => { | |||
const services = xhr.response['items']; | |||
const proxies = services.filter(svc => (svc.service_type === 'proxy')); | |||
const n = Math.floor(Math.random() * proxies.length); | |||
proxy = proxies[n]; | |||
}); | |||
const blocks = []; | |||
for (let i = 0; i < blockRefs.length; i++) { | |||
let locator, start, end; | |||
prom = prom.then(() => { | |||
[ locator, start, end ] = blockRefs[i]; | |||
return makeArvadosRequest( | |||
proxy.service_host + ':' + proxy.service_port, | |||
arvToken, '/' + locator, | |||
{ 'useSsl': proxy.service_ssl_flag, | |||
'responseType': 'arraybuffer' } | |||
); | |||
}); | |||
prom = prom.then(xhr => blocks.push(xhr.response.slice(start, end))); | |||
} | |||
prom = prom.then(() => { | |||
const url = URL.createObjectURL(new Blob(blocks)); | |||
const totalSize = blocks.reduce((a, b) => a.length + b.length); | |||
const big = new Uint8Array(totalSize); | |||
for (let i = 0, pos = 0; i < blocks.length; i++) { | |||
big.set(blocks[i], pos); | |||
pos += blocks[i].length; | |||
} | |||
// papayaContainers[0].startPapaya(); | |||
const poll = () => { | |||
setTimeout(() => { | |||
console.log('Polling Papaya startup...') | |||
if (true) { // window.papayaContainers && window.papayaContainers[0]) { | |||
console.log('Great, Papaya started!'); | |||
papaya.Container.startPapaya(); | |||
//papaya.Container.addImage(0, big.buffer); | |||
document.body.id = "bod"; | |||
document.body.style.background = "#555"; | |||
papaya.Container.addViewer("bod", { | |||
'binaryImages': [ big.buffer ], | |||
'noNewFiles': true, | |||
}); | |||
} else | |||
poll(); | |||
}, 1000); | |||
}; | |||
//setTimeout(poll, 10000); | |||
poll(); | |||
}); | |||
} | |||
class WBImageViewerPage extends Component { | |||
componentDidMount() { | |||
const { blobUrl, app } = this.props; | |||
const { arvHost, arvToken } = app.state; | |||
let prom = new Promise((accept, reject) => { | |||
const xhr = new XMLHttpRequest(); | |||
xhr.open('GET', blobUrl); | |||
xhr.onreadystatechange = () => { | |||
if (xhr.readyState !== 4) return; | |||
if (xhr.status !== 200) reject(xhr); | |||
else accept(xhr); | |||
}; | |||
xhr.responseType = 'blob'; | |||
xhr.send(); | |||
}); | |||
prom = prom.then(xhr => xhr.response.text()); | |||
prom = prom.then(data => { | |||
data = JSON.parse(data); | |||
downloadFile(arvHost, arvToken, data.file); | |||
}); | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
<script language="javascript" src="/js/papaya.js"></script> | |||
<div id="papaya" style="width: auto; height: 100%; margin: 0;"></div> | |||
</div> | |||
); | |||
} | |||
} | |||
export default WBImageViewerPage; |