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!
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
8.6KB

  1. import { h, Component } from 'preact';
  2. import WBTable from 'wb-table';
  3. import WBBreadcrumbs from 'wb-breadcrumbs';
  4. import WBPagination from 'wb-pagination';
  5. import makeArvadosRequest from 'make-arvados-request';
  6. import wbDownloadFile from 'wb-download-file';
  7. import WBManifestWorkerWrapper from 'wb-manifest-worker-wrapper';
  8. function unescapeName(name) {
  9. return name.replace(/(\\\\|\\[0-9]{3})/g,
  10. (_, $1) => ($1 === '\\\\' ? '\\' : String.fromCharCode(parseInt($1.substr(1), 8))));
  11. }
  12. function encodeURIComponentIncludingDots(s) {
  13. return encodeURIComponent(s).replace('.', '%2E');
  14. }
  15. function endsWith(what, endings) {
  16. if (typeof(endings) === 'string')
  17. return what.endsWith(endings);
  18. if (endings instanceof Array)
  19. return endings.map(a => what.endsWith(a)).reduce((a, b) => (a || b));
  20. throw Error('Expected second argument to be either a string or an array');
  21. }
  22. function maskRows(rows) {
  23. return rows.map(r => r.map(c => '-'));
  24. }
  25. class WBCollectionContent extends Component {
  26. constructor(...args) {
  27. super(...args);
  28. this.state.rows = [];
  29. this.state.manifestWorker = new WBManifestWorkerWrapper();
  30. this.state.loaded = 0;
  31. this.state.total = 0;
  32. this.state.mode = 'manifestDownload';
  33. this.state.parsedStreams = 0;
  34. this.state.totalStreams = 1;
  35. }
  36. getUrl(params) {
  37. let res = '/collection-browse/' +
  38. ('uuid' in params ? params.uuid : this.props.uuid) + '/' +
  39. encodeURIComponentIncludingDots('collectionPath' in params ? params.collectionPath : this.props.collectionPath) + '/' +
  40. ('page' in params ? params.page : this.props.page);
  41. return res;
  42. }
  43. componentDidMount() {
  44. let { arvHost, arvToken } = this.props.app.state;
  45. let { uuid, collectionPath } = this.props;
  46. let { manifestWorker } = this.state;
  47. let select = [ 'manifest_text' ];
  48. let prom = makeArvadosRequest(arvHost, arvToken,
  49. '/arvados/v1/collections/' + uuid +
  50. '?select=' + encodeURIComponent(JSON.stringify(select)),
  51. { 'onProgress': e => {
  52. this.setState({ 'loaded': e.loaded, 'total': e.total });
  53. } });
  54. prom = prom.then(xhr => {
  55. const streams = xhr.response.manifest_text.split('\n');
  56. const paths = streams.filter(s => s).map(s => {
  57. const n = s.indexOf(' ');
  58. return unescapeName(s.substr(0, n));
  59. });
  60. let prom_1 = new Promise(accept => accept());
  61. prom_1 = prom_1.then(() => {
  62. this.setState({
  63. 'totalStreams': streams.length,
  64. 'parsedStreams': 0,
  65. 'mode': 'manifestParse'
  66. });
  67. return manifestWorker.postMessage([ 'precreatePaths', paths ]);
  68. });
  69. let lastListingTimestamp = new Date(0);
  70. for (let i = 0; i < streams.length; i++) {
  71. prom_1 = prom_1.then(() => manifestWorker.postMessage([ 'parseStream', streams[i] ]));
  72. prom_1 = prom_1.then(() => {
  73. if (new Date() - lastListingTimestamp < 1000)
  74. return;
  75. lastListingTimestamp = new Date();
  76. let prom_2 = new Promise(accept => accept());
  77. prom_2 = prom_2.then(() => manifestWorker.postMessage([
  78. 'listDirectory', '.' + this.props.collectionPath, true
  79. ]));
  80. prom_2 = prom_2.then(e => {
  81. this.prepareRows(e.data[1]);
  82. this.setState({ 'parsedStreams': (i + 1) });
  83. });
  84. return prom_2;
  85. });
  86. }
  87. prom_1 = prom_1.then(() => manifestWorker.postMessage([ 'listDirectory',
  88. '.' + this.props.collectionPath, true ]));
  89. prom_1 = prom_1.then(e => {
  90. this.state.mode = 'browsingReady';
  91. this.prepareRows(e.data[1]);
  92. });
  93. return prom_1;
  94. });
  95. }
  96. componentWillReceiveProps(nextProps) {
  97. this.props = nextProps;
  98. this.setState({ rows: maskRows(this.state.rows) });
  99. const { manifestWorker, mode } = this.state;
  100. const { collectionPath } = this.props;
  101. if (mode === 'browsingReady') {
  102. let prom = manifestWorker.postMessage([ 'listDirectory', '.' + collectionPath, true ]);
  103. prom = prom.then(e => this.prepareRows(e.data[1]));
  104. }
  105. }
  106. prepareRows(listing) {
  107. let { manifestWorker, mode } = this.state;
  108. let { collectionPath, page, itemsPerPage, app } = this.props;
  109. let { arvHost, arvToken } = app.state;
  110. const numPages = Math.ceil(listing.length / itemsPerPage);
  111. listing = listing.slice(page * itemsPerPage,
  112. page * itemsPerPage + itemsPerPage);
  113. this.setState({
  114. 'numPages': numPages,
  115. 'rows': listing.map(item => (
  116. (item[0] === 'd') ? [
  117. (<a href={ this.getUrl({ 'collectionPath': collectionPath + '/' + item[1], 'page': 0 }) }>{ item[1] }/</a>),
  118. 'Directory',
  119. null,
  120. (<div></div>)
  121. ] : [
  122. item[1],
  123. 'File',
  124. filesize(item[2]),
  125. ( (mode === 'browsingReady') ? (
  126. <div>
  127. <button class="btn btn-outline-primary mx-1" title="Download"
  128. onclick={ () => manifestWorker.postMessage([ 'getFile',
  129. '.' + collectionPath + '/' + item[1] ]).then(e => {
  130. const file = e.data[1];
  131. const blob = new Blob([
  132. JSON.stringify([ arvHost, arvToken, item[1], file ])
  133. ]);
  134. const blocksBlobUrl = URL.createObjectURL(blob);
  135. window.open('/download/' + encodeURIComponent(blocksBlobUrl), '_blank');
  136. }) }><i class="fas fa-download"></i></button>
  137. <button class="btn btn-outline-primary mx-1" title="View"
  138. onclick={ () => {
  139. alert('Not implemented.')
  140. } }><i class="far fa-eye"></i></button>
  141. { endsWith(item[1].toLowerCase(), ['.nii', '.nii.gz']) ? (
  142. <button class="btn btn-outline-primary mx-1" title="View Image"
  143. onclick={ () => manifestWorker.postMessage([ 'getFile',
  144. '.' + collectionPath + '/' + item[1] ]).then(e => {
  145. const file = e.data[1];
  146. const blob = new Blob([
  147. JSON.stringify({ 'name': item[1], 'file': file })
  148. ]);
  149. const blocksBlobUrl = URL.createObjectURL(blob);
  150. window.open('/image-viewer/' + encodeURIComponent(blocksBlobUrl), '_blank');
  151. }) }><i class="fas fa-image"></i></button>
  152. ) : null }
  153. </div>
  154. ) : null)
  155. ]
  156. ))
  157. });
  158. }
  159. render({ collectionPath, page }, { manifestReader, rows,
  160. numPages, loaded, total, mode, parsedStreams, totalStreams }) {
  161. return (
  162. <div>
  163. <WBBreadcrumbs items={ ('.' + collectionPath).split('/').map((name, index) => ({ name, index })) }
  164. getItemUrl={ it => this.getUrl({
  165. collectionPath: ('.' + collectionPath).split('/').slice(0, it.index + 1).join('/').substr(1),
  166. page: 0
  167. }) } />
  168. { (mode === 'manifestDownload') ?
  169. (
  170. <div class="container-fluid">
  171. <div>Downloading manifest: { filesize(loaded) }</div>
  172. <div class="progress">
  173. <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
  174. aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
  175. </div>
  176. </div>
  177. ) : (
  178. <div>
  179. { mode === 'manifestParse' ? (
  180. <div class="container-fluid mb-2">
  181. <div>Parsing manifest: { parsedStreams }/{ totalStreams }</div>
  182. <div class="progress">
  183. <div class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
  184. aria-valuenow={ totalStreams } aria-valuemin="0" aria-valuemax={ parsedStreams } style={ 'width: ' + Math.round(parsedStreams * 100 / totalStreams) + '%' }></div>
  185. </div>
  186. </div>
  187. ) : null }
  188. <WBTable columns={ [ 'Name', 'Type', 'Size', 'Actions' ] }
  189. rows={ rows } />
  190. <WBPagination activePage={ page } numPages={ numPages }
  191. getPageUrl={ page => this.getUrl({ 'page': page }) } />
  192. </div>
  193. ) }
  194. </div>
  195. );
  196. }
  197. }
  198. WBCollectionContent.defaultProps = {
  199. 'collectionPath': '',
  200. 'page': 0,
  201. 'itemsPerPage': 20
  202. };
  203. export default WBCollectionContent;