IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
Browse Source

Attempt to download inline but doesn't seem to work.

pull/1/head
parent
commit
589a1aece8
4 changed files with 314 additions and 4 deletions
  1. +1
    -1
      frontend/rollup.config.js
  2. +1
    -1
      frontend/src/js/page/wb-app.js
  3. +16
    -2
      frontend/src/js/page/wb-download-page.js
  4. +296
    -0
      frontend/src/js/thirdparty/StreamSaver.js

+ 1
- 1
frontend/rollup.config.js View File

@@ -49,7 +49,7 @@ export default {
'node_modules/js-yaml/dist/js-yaml.min.js': 'dist/js/js-yaml.min.js',
'node_modules/streamsaver/mitm.html': 'dist/mitm.html',
'node_modules/streamsaver/sw.js': 'dist/sw.js',
'node_modules/streamsaver/StreamSaver.js': 'dist/js/StreamSaver.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',
verbose: true
}),


+ 1
- 1
frontend/src/js/page/wb-app.js View File

@@ -93,7 +93,7 @@ class WBApp extends Component {
<WBLaunchWorkflowPage path="/workflow-launch/:workflowUuid" app={ this } />
<WBDownloadPage path="/download/:blocksBlobUrl" app={ this } />
<WBDownloadPage path="/download/:blocksBlobUrl/:inline?" app={ this } />
</Router>
);
}


+ 16
- 2
frontend/src/js/page/wb-download-page.js View File

@@ -1,9 +1,21 @@
import { h, Component } from 'preact';
import makeArvadosRequest from 'make-arvados-request';
function contentTypeFromFilename(name) {
let ext = name.split('.');
ext = ext[ext.length - 1].toUpperCase();
if (ext === 'TXT')
return 'text/plain; charset=utf-8';
if (ext === 'JPG' || ext === 'JPEG')
return 'image/jpeg';
if (ext === 'PNG')
return 'image/png';
return 'application/octet-stream; charset=utf-8';
}
class WBDownloadPage extends Component {
componentDidMount() {
const { app, blocksBlobUrl } = this.props;
const { app, blocksBlobUrl, inline } = this.props;
const { arvHost, arvToken } = app.state;
let prom = new Promise((accept, reject) => {
@@ -39,7 +51,9 @@ class WBDownloadPage extends Component {
[ _, _, name, file ] = JSON.parse(text);
fileStream = streamSaver.createWriteStream(name, {
size: file[1]
size: file[1],
inline: inline,
contentType: contentTypeFromFilename(name)
});
writer = fileStream.getWriter();


+ 296
- 0
frontend/src/js/thirdparty/StreamSaver.js View File

@@ -0,0 +1,296 @@
/* global chrome location ReadableStream define MessageChannel TransformStream */

;((name, definition) => {
typeof module !== 'undefined'
? module.exports = definition()
: typeof define === 'function' && typeof define.amd === 'object'
? define(definition)
: this[name] = definition()
})('streamSaver', () => {
'use strict'

let mitmTransporter = null
let supportsTransferable = false
const test = fn => { try { fn() } catch (e) {} }
const ponyfill = window.WebStreamsPolyfill || {}
const isSecureContext = window.isSecureContext
let useBlobFallback = /constructor/i.test(window.HTMLElement) || !!window.safari
const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style
? 'iframe'
: 'navigate'

const streamSaver = {
createWriteStream,
WritableStream: window.WritableStream || ponyfill.WritableStream,
supported: true,
version: { full: '2.0.0', major: 2, minor: 0, dot: 0 },
mitm: 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0'
}

/**
* create a hidden iframe and append it to the DOM (body)
*
* @param {string} src page to load
* @return {HTMLIFrameElement} page to load
*/
function makeIframe (src) {
if (!src) throw new Error('meh')
const iframe = document.createElement('iframe')
iframe.hidden = true
iframe.src = src
iframe.loaded = false
iframe.name = 'iframe'
iframe.isIframe = true
iframe.postMessage = (...args) => iframe.contentWindow.postMessage(...args)
iframe.addEventListener('load', () => {
iframe.loaded = true
}, { once: true })
document.body.appendChild(iframe)
return iframe
}

/**
* create a popup that simulates the basic things
* of what a iframe can do
*
* @param {string} src page to load
* @return {object} iframe like object
*/
function makePopup (src) {
const options = 'width=200,height=100'
const delegate = document.createDocumentFragment()
const popup = {
frame: window.open(src, 'popup', options),
loaded: false,
isIframe: false,
isPopup: true,
remove () { popup.frame.close() },
addEventListener (...args) { delegate.addEventListener(...args) },
dispatchEvent (...args) { delegate.dispatchEvent(...args) },
removeEventListener (...args) { delegate.removeEventListener(...args) },
postMessage (...args) { popup.frame.postMessage(...args) }
}

const onReady = evt => {
if (evt.source === popup.frame) {
popup.loaded = true
window.removeEventListener('message', onReady)
popup.dispatchEvent(new Event('load'))
}
}

window.addEventListener('message', onReady)

return popup
}

try {
// We can't look for service worker since it may still work on http
new Response(new ReadableStream())
if (isSecureContext && !('serviceWorker' in navigator)) {
useBlobFallback = true
}
} catch (err) {
useBlobFallback = true
}

test(() => {
// Transfariable stream was first enabled in chrome v73 behind a flag
const { readable } = new TransformStream()
const mc = new MessageChannel()
mc.port1.postMessage(readable, [readable])
mc.port1.close()
mc.port2.close()
supportsTransferable = true
// Freeze TransformStream object (can only work with native)
Object.defineProperty(streamSaver, 'TransformStream', {
configurable: false,
writable: false,
value: TransformStream
})
})

function loadTransporter () {
if (!mitmTransporter) {
mitmTransporter = isSecureContext
? makeIframe(streamSaver.mitm)
: makePopup(streamSaver.mitm)
}
}

/**
* @param {string} filename filename that should be used
* @param {object} options [description]
* @param {number} size depricated
* @return {WritableStream}
*/
function createWriteStream (filename, options, size) {
let opts = {
size: null,
pathname: null,
writableStrategy: undefined,
readableStrategy: undefined
}

// normalize arguments
if (Number.isFinite(options)) {
[ size, options ] = [ options, size ]
console.warn('[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream')
opts.size = size
opts.writableStrategy = options
} else if (options && options.highWaterMark) {
console.warn('[StreamSaver] Depricated pass an object as 2nd argument when creating a write stream')
opts.size = size
opts.writableStrategy = options
} else {
opts = options || {}
}
if (!useBlobFallback) {
loadTransporter()

var bytesWritten = 0 // by StreamSaver.js (not the service worker)
var downloadUrl = null
var channel = new MessageChannel()

// Make filename RFC5987 compatible
filename = encodeURIComponent(filename.replace(/\//g, ':'))
.replace(/['()]/g, escape)
.replace(/\*/g, '%2A')

const response = {
transferringReadable: supportsTransferable,
pathname: opts.pathname || Math.random().toString().slice(-6) + '/' + filename,
headers: {
'Content-Type': (options.contentType ? options.contentType :
'application/octet-stream; charset=utf-8'),
'Content-Disposition': (options.inline ? 'inline' :
("attachment; filename*=UTF-8''" + filename))
}
}

if (opts.size) {
response.headers['Content-Length'] = opts.size
}

const args = [ response, '*', [ channel.port2 ] ]

if (supportsTransferable) {
const transformer = downloadStrategy === 'iframe' ? undefined : {
// This transformer & flush method is only used by insecure context.
transform (chunk, controller) {
bytesWritten += chunk.length
controller.enqueue(chunk)

if (downloadUrl) {
location.href = downloadUrl
downloadUrl = null
}
},
flush () {
if (downloadUrl) {
location.href = downloadUrl
}
}
}
var ts = new streamSaver.TransformStream(
transformer,
opts.writableStrategy,
opts.readableStrategy
)
const readableStream = ts.readable

channel.port1.postMessage({ readableStream }, [ readableStream ])
}

channel.port1.onmessage = evt => {
// Service worker sent us a link that we should open.
if (evt.data.download) {
// Special treatment for popup...
if (downloadStrategy === 'navigate') {
mitmTransporter.remove()
mitmTransporter = null
if (bytesWritten) {
location.href = evt.data.download
} else {
downloadUrl = evt.data.download
}
} else {
if (mitmTransporter.isPopup) {
mitmTransporter.remove()
// Special case for firefox, they can keep sw alive with fetch
if (downloadStrategy === 'iframe') {
makeIframe(streamSaver.mitm)
}
}

// We never remove this iframes b/c it can interrupt saving
makeIframe(evt.data.download)
}
}
}

if (mitmTransporter.loaded) {
mitmTransporter.postMessage(...args)
} else {
mitmTransporter.addEventListener('load', () => {
mitmTransporter.postMessage(...args)
}, { once: true })
}
}

let chunks = []

return (!useBlobFallback && ts && ts.writable) || new streamSaver.WritableStream({
write (chunk) {
if (useBlobFallback) {
// Safari... The new IE6
// https://github.com/jimmywarting/StreamSaver.js/issues/69
//
// even doe it has everything it fails to download anything
// that comes from the service worker..!
chunks.push(chunk)
return
}

// is called when a new chunk of data is ready to be written
// to the underlying sink. It can return a promise to signal
// success or failure of the write operation. The stream
// implementation guarantees that this method will be called
// only after previous writes have succeeded, and never after
// close or abort is called.

// TODO: Kind of important that service worker respond back when
// it has been written. Otherwise we can't handle backpressure
// EDIT: Transfarable streams solvs this...
channel.port1.postMessage(chunk)
bytesWritten += chunk.length

if (downloadUrl) {
location.href = downloadUrl
downloadUrl = null
}
},
close () {
if (useBlobFallback) {
const blob = new Blob(chunks, { type: 'application/octet-stream; charset=utf-8' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
} else {
channel.port1.postMessage('end')
}
},
abort () {
chunks = []
channel.port1.postMessage('abort')
channel.port1.onmessage = null
channel.port1.close()
channel.port2.close()
channel = null
}
}, opts.writableStrategy)
}

return streamSaver
})

Loading…
Cancel
Save