diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index 2456afcc2d..d213587064 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -109,6 +109,31 @@ module.exports = { require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/shared-file-view-pdf.js", ], + sharedFileViewSVG: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-svg.js", + ], + sharedFileViewAudio: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-audio.js", + ], + sharedFileViewDocument: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-document.js", + ], + sharedFileViewSpreadsheet: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-spreadsheet.js", + ], + sharedFileViewUnknown: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-unknown.js", + ], viewFileText: [ require.resolve('./polyfills'), require.resolve('react-dev-utils/webpackHotDevClient'), diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index 7d8ffcb3a1..160f5f07e9 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -70,6 +70,11 @@ module.exports = { sharedFileViewImage: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-image.js"], sharedFileViewVideo: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-video.js"], sharedFileViewPDF: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-pdf.js"], + sharedFileViewSVG: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-svg.js"], + sharedFileViewAudio: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-audio.js"], + sharedFileViewDocument: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-document.js"], + sharedFileViewSpreadsheet: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-spreadsheet.js"], + sharedFileViewUnknown: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-unknown.js"], viewFileText: [require.resolve('./polyfills'), paths.appSrc + "/view-file-text.js"], viewFileImage: [require.resolve('./polyfills'), paths.appSrc + "/view-file-image.js"], viewFileXmind: [require.resolve('./polyfills'), paths.appSrc + "/view-file-xmind.js"], diff --git a/frontend/src/components/shared-file-view/shared-file-view-tip.js b/frontend/src/components/shared-file-view/shared-file-view-tip.js index e5d31c998b..90170bf150 100644 --- a/frontend/src/components/shared-file-view/shared-file-view-tip.js +++ b/frontend/src/components/shared-file-view/shared-file-view-tip.js @@ -1,7 +1,12 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { gettext } from '../../utils/constants'; -const { err } = window.shared.pageOptions; +const { err, trafficOverLimit } = window.shared.pageOptions; + +const propTypes = { + errorMsg: PropTypes.string +}; class SharedFileViewTip extends React.Component { render() { @@ -9,16 +14,21 @@ class SharedFileViewTip extends React.Component { if (err == 'File preview unsupported') { errorMsg =

{gettext('Online view is not applicable to this file format')}

; } else { - errorMsg =

{err}

; + errorMsg =

{err || this.props.errorMsg}

; } return (
{errorMsg} + {!trafficOverLimit && + {gettext('Download')} + }
); } } +SharedFileViewTip.propTypes = propTypes; + export default SharedFileViewTip; diff --git a/frontend/src/components/shared-file-view/shared-file-view.js b/frontend/src/components/shared-file-view/shared-file-view.js index e644f21864..33e3d32469 100644 --- a/frontend/src/components/shared-file-view/shared-file-view.js +++ b/frontend/src/components/shared-file-view/shared-file-view.js @@ -45,7 +45,7 @@ class SharedFileView extends React.Component { } componentDidMount() { - if (trafficOverLimit == 'True') { + if (trafficOverLimit) { toaster.danger(gettext('File download is disabled: the share link traffic of owner is used up.'), { duration: 3 }); @@ -76,7 +76,7 @@ class SharedFileView extends React.Component { onClick={this.handleSaveSharedFileDialog}>{gettext('Save as ...')} }{' '} - {(trafficOverLimit === 'False') && + {!trafficOverLimit && diff --git a/frontend/src/shared-file-view-audio.js b/frontend/src/shared-file-view-audio.js new file mode 100644 index 0000000000..0b7a8d7dab --- /dev/null +++ b/frontend/src/shared-file-view-audio.js @@ -0,0 +1,44 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import SharedFileView from './components/shared-file-view/shared-file-view'; +import SharedFileViewTip from './components/shared-file-view/shared-file-view-tip'; +import AudioPlayer from './components/audio-player'; + +import './css/audio-file-view.css'; + +const { rawPath, err } = window.shared.pageOptions; + +class SharedFileViewAudio extends React.Component { + render() { + return } />; + } +} + +class FileContent extends React.Component { + render() { + if (err) { + return ; + } + + const videoJsOptions = { + autoplay: false, + controls: true, + preload: 'auto', + sources: [{ + src: rawPath + }] + }; + return ( +
+
+ +
+
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/shared-file-view-document.js b/frontend/src/shared-file-view-document.js new file mode 100644 index 0000000000..11f4a792db --- /dev/null +++ b/frontend/src/shared-file-view-document.js @@ -0,0 +1,109 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { seafileAPI } from './utils/seafile-api'; +import { gettext, mediaUrl} from './utils/constants'; +import SharedFileView from './components/shared-file-view/shared-file-view'; +import SharedFileViewTip from './components/shared-file-view/shared-file-view-tip'; +import Loading from './components/loading'; +import PDFViewer from './components/pdf-viewer'; + +import './css/pdf-file-view.css'; + +const { + repoID, filePath, err, + commitID, fileType, sharedToken +} = window.shared.pageOptions; + +class SharedFileViewDocument extends React.Component { + render() { + return } />; + } +} + +class FileContent extends React.Component { + + constructor(props) { + super(props); + this.state = { + isLoading: !err, + errorMsg: '' + }; + } + + componentDidMount() { + if (err) { + return; + } + + let queryStatus = () => { + seafileAPI.queryOfficeFileConvertStatus(repoID, commitID, filePath, fileType.toLowerCase(), sharedToken).then((res) => { + const convertStatus = res.data['status']; + switch (convertStatus) { + case 'PROCESSING': + this.setState({ + isLoading: true + }); + setTimeout(queryStatus, 2000); + break; + case 'ERROR': + this.setState({ + isLoading: false, + errorMsg: gettext('Document convertion failed.') + }); + break; + case 'DONE': + this.setState({ + isLoading: false, + errorMsg: '' + }); + + let scriptNode = document.createElement('script'); + scriptNode.type = 'text/javascript'; + scriptNode.src = `${mediaUrl}js/pdf/viewer.js`; + document.body.append(scriptNode); + } + }).catch((error) => { + if (error.response) { + this.setState({ + isLoading: false, + errorMsg: gettext('Document convertion failed.') + }); + } else { + this.setState({ + isLoading: false, + errorMsg: gettext('Please check the network.') + }); + } + }); + }; + + queryStatus(); + } + + render() { + const { isLoading, errorMsg } = this.state; + + if (err) { + return ; + } + + if (isLoading) { + return ; + } + + if (errorMsg) { + return ; + } + + return ( +
+ +
+ ); + } +} + +ReactDOM.render ( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/shared-file-view-spreadsheet.js b/frontend/src/shared-file-view-spreadsheet.js new file mode 100644 index 0000000000..7ced8b29fc --- /dev/null +++ b/frontend/src/shared-file-view-spreadsheet.js @@ -0,0 +1,111 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { seafileAPI } from './utils/seafile-api'; +import { siteRoot, gettext } from './utils/constants'; +import SharedFileView from './components/shared-file-view/shared-file-view'; +import SharedFileViewTip from './components/shared-file-view/shared-file-view-tip'; +import Loading from './components/loading'; + +import './css/spreadsheet-file-view.css'; + +const { + repoID, filePath, err, + commitID, fileType, fileName, sharedToken +} = window.shared.pageOptions; + +class SharedFileViewSpreadsheet extends React.Component { + render() { + return ( + } /> + ); + } +} + +class FileContent extends React.Component { + + constructor(props) { + super(props); + this.state = { + isLoading: !err, + errorMsg: '' + }; + } + + componentDidMount() { + if (err) { + return; + } + + let queryStatus = () => { + seafileAPI.queryOfficeFileConvertStatus(repoID, commitID, filePath, fileType.toLowerCase(), sharedToken).then((res) => { + const convertStatus = res.data['status']; + switch (convertStatus) { + case 'QUEUED': + case 'PROCESSING': + this.setState({ + isLoading: true + }); + setTimeout(queryStatus, 2000); + break; + case 'ERROR': + this.setState({ + isLoading: false, + errorMsg: gettext('Document convertion failed.') + }); + break; + case 'DONE': + this.setState({ + isLoading: false, + errorMsg: '' + }); + } + }).catch((error) => { + if (error.response) { + this.setState({ + isLoading: false, + errorMsg: gettext('Document convertion failed.') + }); + } else { + this.setState({ + isLoading: false, + errorMsg: gettext('Please check the network.') + }); + } + }); + }; + + queryStatus(); + } + + setIframeHeight = (e) => { + const iframe = e.currentTarget; + iframe.height = iframe.contentDocument.body.scrollHeight; + } + + render() { + const { isLoading, errorMsg } = this.state; + + if (err) { + return ; + } + + if (isLoading) { + return ; + } + + if (errorMsg) { + return ; + } + + return ( +
+ +
+ ); + } +} + +ReactDOM.render ( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/shared-file-view-svg.js b/frontend/src/shared-file-view-svg.js new file mode 100644 index 0000000000..94c5fe7ef3 --- /dev/null +++ b/frontend/src/shared-file-view-svg.js @@ -0,0 +1,35 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import SharedFileView from './components/shared-file-view/shared-file-view'; +import SharedFileViewTip from './components/shared-file-view/shared-file-view-tip'; + +import './css/svg-file-view.css'; + +const { fileName, rawPath, err } = window.shared.pageOptions; + +class SharedFileViewSVG extends React.Component { + render() { + return } />; + } +} + +class FileContent extends React.Component { + render() { + if (err) { + return ; + } + + return ( +
+
+ {fileName} +
+
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/shared-file-view-unknown.js b/frontend/src/shared-file-view-unknown.js new file mode 100644 index 0000000000..03ee5f60db --- /dev/null +++ b/frontend/src/shared-file-view-unknown.js @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import SharedFileView from './components/shared-file-view/shared-file-view'; +import SharedFileViewTip from './components/shared-file-view/shared-file-view-tip'; + +const { err } = window.shared.pageOptions; + +class SharedFileViewImage extends React.Component { + render() { + return } />; + } +} + +class FileContent extends React.Component { + render() { + if (err) { + return ; + } + } +} + +ReactDOM.render( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/shared-file-view-video.js b/frontend/src/shared-file-view-video.js index ff201d73a0..73a7ff3c93 100644 --- a/frontend/src/shared-file-view-video.js +++ b/frontend/src/shared-file-view-video.js @@ -1,150 +1,44 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Account from './components/common/account'; -import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants'; -import { Button } from 'reactstrap'; -import { Utils } from './utils/utils'; -import SaveSharedFileDialog from './components/dialog/save-shared-file-dialog'; -import toaster from './components/toast'; +import SharedFileView from './components/shared-file-view/shared-file-view'; +import SharedFileViewTip from './components/shared-file-view/shared-file-view-tip'; import VideoPlayer from './components/video-player'; -import watermark from 'watermark-dom'; -import './css/shared-file-view.css'; import './css/video-file-view.css'; -let loginUser = window.app.pageOptions.name; -const { repoID, sharedToken, trafficOverLimit, fileName, fileSize, rawPath, sharedBy, siteName, enableWatermark, download, err } = window.shared.pageOptions; - -class SharedFileViewVideo extends React.Component { - - constructor(props) { - super(props); - this.state = { - showSaveSharedFileDialog: false - }; - } - - handleSaveSharedFileDialog = () => { - this.setState({ - showSaveSharedFileDialog: true - }); - } - - toggleCancel = () => { - this.setState({ - showSaveSharedFileDialog: false - }); - } - - handleSaveSharedFile = () => { - toaster.success(gettext('Successfully saved'), { - duration: 3 - }); - } - - componentDidMount() { - if (trafficOverLimit == 'True') { - toaster.danger(gettext('File download is disabled: the share link traffic of owner is used up.'), { - duration: 3 - }); - } - } - - getContent() { - if (err) { - let errorMsg; - if (err == 'File preview unsupported') { - errorMsg =

{gettext('Online view is not applicable to this file format')}

; - } else { - errorMsg =

{err}

; - } - return ( -
-
- {errorMsg} -
-
- ); - } else { - const videoJsOptions = { - autoplay: false, - controls: true, - preload: 'auto', - sources: [{ - src: rawPath - }] - }; - return ( -
-
- -
-
- ); - } - } +const { rawPath, err } = window.shared.pageOptions; +class SharedFileViewImage extends React.Component { render() { + return } />; + } +} + +class FileContent extends React.Component { + render() { + if (err) { + return ; + } + + const videoJsOptions = { + autoplay: false, + controls: true, + preload: 'auto', + sources: [{ + src: rawPath + }] + }; return ( -
-
- - - logo - - - { loginUser && } +
+
+
-
-
-
-

{fileName}

-

{gettext('Shared by:')}{' '}{sharedBy}

-
- {download && -
- {(loginUser && loginUser !== sharedBy) && - - }{' '} - {(trafficOverLimit === 'False') && - - } -
- } -
- {this.getContent()} -
- {this.state.showSaveSharedFileDialog && - - }
); } } -if (enableWatermark) { - let watermark_txt; - if (loginUser) { - watermark_txt = siteName + ' ' + loginUser; - } else { - watermark_txt = gettext('Anonymous User'); - } - watermark.init({ - watermark_txt: watermark_txt, - watermark_alpha: 0.075 - }); -} - ReactDOM.render( - , + , document.getElementById('wrapper') ); diff --git a/seahub/templates/shared_file_view_react.html b/seahub/templates/shared_file_view_react.html index 98db73858c..2f62490d80 100644 --- a/seahub/templates/shared_file_view_react.html +++ b/seahub/templates/shared_file_view_react.html @@ -9,11 +9,22 @@ {% render_bundle 'sharedFileViewText' 'css' %} {% elif filetype == 'Image' %} {% render_bundle 'sharedFileViewImage' 'css' %} +{% elif filetype == 'SVG' %} + {% render_bundle 'sharedFileViewSVG' 'css' %} {% elif filetype == 'Video' %} {% render_bundle 'sharedFileViewVideo' 'css' %} +{% elif filetype == 'Audio' %} + {% render_bundle 'sharedFileViewAudio' 'css' %} {% elif filetype == 'PDF' %} {% render_bundle 'sharedFileViewPDF' 'css' %} +{% elif filetype == 'Document' %} + + {% render_bundle 'sharedFileViewDocument' 'css' %} +{% elif filetype == 'SpreadSheet' %} + {% render_bundle 'sharedFileViewSpreadsheet' 'css' %} +{% elif filetype == 'Unknown' %} + {% render_bundle 'sharedFileViewUnknown' 'css' %} {% endif %} {% endblock %} @@ -22,21 +33,21 @@ window.shared = { pageOptions: { repoID: '{{ repo.id }}', - path: '{{ path|escapejs }}', + filePath: '{{ path|escapejs }}', sharedToken: '{{ shared_token }}', - trafficOverLimit: '{{ traffic_over_limit }}', + trafficOverLimit: {% if traffic_over_limit %}true{% else %}false{% endif %}, fileName: '{{ file_name|escapejs }}', - fileSize: '{{ file_size }}', + fileSize: {{ file_size }}, rawPath: '{{ raw_path|escapejs }}', sharedBy: '{{ shared_by|email2nickname }}', siteName: '{{ site_name }}', - enableWatermark: '{{ enable_watermark }}' == 'True', + enableWatermark: {% if enable_watermark %}true{% else %}false{% endif %}, download: '{{ permissions.can_download }}' == 'True', - fileEncodingList: '{{ file_encoding_list|escapejs }}', - encoding: '{{ encoding }}', fileContent: '{{ file_content|escapejs }}', - err: '{{ err }}', + err: {% if err %}'{{ err }}'{% else %}''{% endif %}, + fileType: '{{ filetype }}', fileExt: '{{ fileext }}', + commitID: '{{ current_commit.id }}' || '{{ repo.head_cmmt_id }}' } }; @@ -46,8 +57,12 @@ {% render_bundle 'sharedFileViewText' 'js' %} {% elif filetype == 'Image' %} {% render_bundle 'sharedFileViewImage' 'js' %} +{% elif filetype == 'SVG' %} + {% render_bundle 'sharedFileViewSVG' 'js' %} {% elif filetype == 'Video' %} {% render_bundle 'sharedFileViewVideo' 'js' %} +{% elif filetype == 'Audio' %} + {% render_bundle 'sharedFileViewAudio' 'js' %} {% elif filetype == 'PDF' %} {% render_bundle 'sharedFileViewPDF' 'js' %} +{% elif filetype == 'Document' %} + {% render_bundle 'sharedFileViewDocument' 'js' %} + + +{% elif filetype == 'SpreadSheet' %} + {% render_bundle 'sharedFileViewSpreadsheet' 'js' %} +{% elif filetype == 'Unknown' %} + {% render_bundle 'sharedFileViewUnknown' 'js' %} {% endif %} {% endblock %} diff --git a/seahub/templates/spreadsheet_file_view_react.html b/seahub/templates/spreadsheet_file_view_react.html index 2294da62c1..b62f64a23a 100644 --- a/seahub/templates/spreadsheet_file_view_react.html +++ b/seahub/templates/spreadsheet_file_view_react.html @@ -12,7 +12,4 @@ {% block render_bundle %} {% render_bundle 'viewFileSpreadsheet' 'js' %} - {% endblock %} diff --git a/seahub/views/file.py b/seahub/views/file.py index 03a4691e54..e67d1d22ce 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -1232,7 +1232,7 @@ def view_shared_file(request, fileshare): template = 'shared_file_view.html' - if is_textual_file(file_type=filetype) or filetype in (IMAGE, VIDEO, PDF): + if filetype != XMIND: template = 'shared_file_view_react.html' return render(request, template, {