diff --git a/frontend/config/webpack.entry.js b/frontend/config/webpack.entry.js index 97fc4db6c7..7d20199442 100644 --- a/frontend/config/webpack.entry.js +++ b/frontend/config/webpack.entry.js @@ -30,6 +30,7 @@ const entryFiles = { viewFileText: '/view-file-text.js', viewFileSdoc: '/view-file-sdoc.js', viewFileDocument: '/view-file-document.js', + viewFileOnlyoffice: '/view-file-onlyoffice.js', viewFileSpreadsheet: '/view-file-spreadsheet.js', settings: '/settings.js', repoHistory: '/repo-history.js', diff --git a/frontend/src/assets/icons/double-arrow-down.svg b/frontend/src/assets/icons/double-arrow-down.svg new file mode 100644 index 0000000000..2c923a931d --- /dev/null +++ b/frontend/src/assets/icons/double-arrow-down.svg @@ -0,0 +1,15 @@ + + + + +unfold + + + + diff --git a/frontend/src/assets/icons/double-arrow-up.svg b/frontend/src/assets/icons/double-arrow-up.svg new file mode 100644 index 0000000000..e3512b1187 --- /dev/null +++ b/frontend/src/assets/icons/double-arrow-up.svg @@ -0,0 +1,15 @@ + + + + +fold + + + + diff --git a/frontend/src/components/dirent-detail/embedded-file-details/index.js b/frontend/src/components/dirent-detail/embedded-file-details/index.js index 17062c448b..18ccd40057 100644 --- a/frontend/src/components/dirent-detail/embedded-file-details/index.js +++ b/frontend/src/components/dirent-detail/embedded-file-details/index.js @@ -10,7 +10,7 @@ import { MetadataContext } from '../../../metadata'; import './index.css'; -const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width = 300, className, component }) => { +const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width = 300, className, component = {} }) => { const { headerComponent } = component; const [direntDetail, setDirentDetail] = useState(''); diff --git a/frontend/src/components/file-view/file-info.js b/frontend/src/components/file-view/file-info.js index ec6c22c2c8..d5f567107d 100644 --- a/frontend/src/components/file-view/file-info.js +++ b/frontend/src/components/file-view/file-info.js @@ -7,7 +7,8 @@ import InternalLinkOperation from '../operations/internal-link-operation'; const propTypes = { toggleStar: PropTypes.func.isRequired, isLocked: PropTypes.bool.isRequired, - isStarred: PropTypes.bool.isRequired + isStarred: PropTypes.bool.isRequired, + isOnlyofficeFile: PropTypes.bool.isRequired }; const { fileName, repoID, filePath, @@ -26,7 +27,7 @@ class FileInfo extends React.PureComponent { }; render() { - const { isStarred, isLocked } = this.props; + const { isStarred, isLocked, isOnlyofficeFile } = this.props; const starredText = isStarred ? gettext('starred') : gettext('unstarred'); const lockedText = gettext('locked'); return ( @@ -41,7 +42,7 @@ class FileInfo extends React.PureComponent { onClick={this.toggleStar}> - {(isPro && isLocked) && + {(isPro && isLocked && !isOnlyofficeFile) && {lockedText} } -
- {latestContributorName} - {moment(lastModificationTime * 1000).format('YYYY-MM-DD HH:mm')} -
+ {!isOnlyofficeFile && ( +
+ {latestContributorName} + {moment(lastModificationTime * 1000).format('YYYY-MM-DD HH:mm')} +
+ )} ); } diff --git a/frontend/src/components/file-view/file-toolbar.js b/frontend/src/components/file-view/file-toolbar.js index 98d9f1bbff..adaa534c24 100644 --- a/frontend/src/components/file-view/file-toolbar.js +++ b/frontend/src/components/file-view/file-toolbar.js @@ -84,8 +84,9 @@ class FileToolbar extends React.Component { return null; } - const { isLocked, lockedByMe } = this.props; const { moreDropdownOpen } = this.state; + + const { isLocked, lockedByMe } = this.props; let showLockUnlockBtn = false; let lockUnlockText; let lockUnlockIcon; if (canLockUnlockFile) { diff --git a/frontend/src/components/file-view/file-view.js b/frontend/src/components/file-view/file-view.js index 6132f5b6a3..5d7a18cabd 100644 --- a/frontend/src/components/file-view/file-view.js +++ b/frontend/src/components/file-view/file-view.js @@ -2,12 +2,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import watermark from 'watermark-dom'; import { seafileAPI } from '../../utils/seafile-api'; -import { siteName } from '../../utils/constants'; +import { gettext, siteName } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import toaster from '../toast'; +import IconButton from '../icon-button'; import FileInfo from './file-info'; import FileToolbar from './file-toolbar'; +import OnlyofficeFileToolbar from './onlyoffice-file-toolbar'; import FileDetails from '../dirent-detail/old-file-details'; +import EmbeddedFileDetails from '../dirent-detail/embedded-file-details'; +import { CollaboratorsProvider, EnableMetadataProvider } from '../../metadata'; import '../../css/file-view.css'; @@ -16,16 +20,16 @@ const propTypes = { content: PropTypes.object.isRequired, isSaving: PropTypes.bool, needSave: PropTypes.bool, + isOnlyofficeFile: PropTypes.bool, participants: PropTypes.array, onParticipantsChange: PropTypes.func, }; const { isStarred, isLocked, lockedByMe, - repoID, filePath, enableWatermark, userNickName, + repoID, filePath, filePerm, enableWatermark, userNickName, repoName, parentDir, fileName } = window.app.pageOptions; - class FileView extends React.Component { constructor(props) { @@ -34,6 +38,7 @@ class FileView extends React.Component { isStarred: isStarred, isLocked: isLocked, lockedByMe: lockedByMe, + isHeaderShown: true, isDetailsPanelOpen: false }; } @@ -93,38 +98,76 @@ class FileView extends React.Component { } }; + toggleHeader = () => { + this.setState({ + isHeaderShown: !this.state.isHeaderShown + }); + }; + render() { - const { isDetailsPanelOpen } = this.state; + const { isOnlyofficeFile = false } = this.props; + const { isDetailsPanelOpen, isHeaderShown } = this.state; return (
-
+
- -
-
- {this.props.content} - {isDetailsPanelOpen && - + {isOnlyofficeFile ? + : + }
+
+ {(isOnlyofficeFile && !isHeaderShown) && + + } + {this.props.content} + {isDetailsPanelOpen && ( + <> + {isOnlyofficeFile ? + + + + + + : + + } + + )} +
); } diff --git a/frontend/src/components/file-view/onlyoffice-file-toolbar.js b/frontend/src/components/file-view/onlyoffice-file-toolbar.js new file mode 100644 index 0000000000..f8b3006fd8 --- /dev/null +++ b/frontend/src/components/file-view/onlyoffice-file-toolbar.js @@ -0,0 +1,95 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import { gettext, siteRoot } from '../../utils/constants'; +import { Utils } from '../../utils/utils'; +import Icon from '../../components/icon'; +import IconButton from '../icon-button'; + +const propTypes = { + toggleDetailsPanel: PropTypes.func.isRequired, + toggleHeader: PropTypes.func.isRequired +}; + +const { + repoID, repoName, parentDir +} = window.app.pageOptions; + +class OnlyofficeFileToolbar extends React.Component { + + constructor(props) { + super(props); + this.state = { + dropdownOpen: false, + moreDropdownOpen: false + }; + } + + toggleMoreOpMenu = () => { + this.setState({ + moreDropdownOpen: !this.state.moreDropdownOpen + }); + }; + + toggle = () => { + this.setState({ + dropdownOpen: !this.state.dropdownOpen + }); + }; + + render() { + const { moreDropdownOpen } = this.state; + return ( + +
+ + + + + + + + {gettext('Open parent folder')} + + + + +
+ + + + + + + {gettext('Fold')} + {gettext('Details')} + + + {gettext('Open parent folder')} + + + + +
+ ); + } +} + +OnlyofficeFileToolbar.propTypes = propTypes; + +export default OnlyofficeFileToolbar; diff --git a/frontend/src/css/file-view.css b/frontend/src/css/file-view.css index c4af35cc9d..7095ed0c52 100644 --- a/frontend/src/css/file-view.css +++ b/frontend/src/css/file-view.css @@ -13,10 +13,39 @@ body { flex-shrink: 0; } +.onlyoffice-file-view-header-shown { + transition: height .3s ease-in-out; + height: 56px; +} + +.onlyoffice-file-view-header-hidden { + transition: all .3s ease-in-out; + display: none!important; +} + +#unfold-onlyoffice-file-view-header { + position: absolute; + top: 0; + right: 40px; + width: 28px; + height: 28px; + display: flex; + justify-content: center; + align-items: center; + background: rgba(255,255,255, 0.6); + border-radius: 0 0 14px 14px; + box-shadow: 0 0 8px rgba(0,0,0, 0.4); + cursor: pointer; +} + +#unfold-onlyoffice-file-view-header:hover { + background: rgba(255,255,255, 1); +} + .file-view-header .file-toolbar-btn { width: 28px; height: 28px; - margin-right: 10px; + margin-left: 10px; cursor: pointer; display: flex; justify-content: center; diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index e676e4be5c..8c04d283ac 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -399,14 +399,14 @@ export const Utils = { if (!dirent) return ''; let size = Utils.isHiDPI() ? 48 : 24; size = isBig ? 192 : size; - if (dirent.isDir()) { + if (dirent.type == 'file') { + return Utils.getFileIconUrl(dirent.name); + } else { let readonly = false; if (dirent.permission && (dirent.permission === 'r' || dirent.permission === 'preview')) { readonly = true; } return Utils.getFolderIconUrl(readonly, size, dirent.has_been_shared_out); - } else { - return Utils.getFileIconUrl(dirent.name); } }, diff --git a/frontend/src/view-file-onlyoffice.js b/frontend/src/view-file-onlyoffice.js new file mode 100644 index 0000000000..76ea6a2ad6 --- /dev/null +++ b/frontend/src/view-file-onlyoffice.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import FileView from './components/file-view/file-view'; +import FileViewTip from './components/file-view/file-view-tip'; + +const { + err +} = window.app.pageOptions; + +class ViewFileOnlyoffice extends React.Component { + render() { + return ( + } isOnlyofficeFile={true} /> + ); + } +} + +class FileContent extends React.Component { + + render() { + if (err) { + return ; + } + + return ( +
+
+
+ ); + } +} + +ReactDom.render(, document.getElementById('wrapper')); diff --git a/seahub/templates/onlyoffice_file_view_react.html b/seahub/templates/onlyoffice_file_view_react.html new file mode 100644 index 0000000000..5fdefa168a --- /dev/null +++ b/seahub/templates/onlyoffice_file_view_react.html @@ -0,0 +1,220 @@ +{% extends 'file_view_react.html' %} +{% load render_bundle from webpack_loader %} +{% load seahub_tags avatar_tags i18n static %} + +{% block extra_style %} +{% render_bundle 'viewFileOnlyoffice' 'css' %} +{% endblock %} + +{% block extra_data %} +{% endblock %} + +{% block render_bundle %} +{% render_bundle 'viewFileOnlyoffice' 'js' %} + +{% get_current_language as LANGUAGE_CODE %} + + + + + +{% if request_from_onlyoffice_desktop_editor %} + +{% endif %} +{% endblock %} diff --git a/seahub/views/file.py b/seahub/views/file.py index 8dfb520234..1a3a732f1a 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -509,7 +509,7 @@ def view_lib_file(request, repo_id, path): token = seafile_api.get_fileserver_access_token( repo_id, file_id, operation, username, use_onetime=settings.FILESERVER_TOKEN_ONCE_ONLY) - + if not token: return render_permission_error(request, _('Unable to view file')) @@ -675,7 +675,7 @@ def view_lib_file(request, repo_id, path): return_dict['seadoc_access_token'] = gen_seadoc_access_token(file_uuid, filename, username, permission=seadoc_perm) # draft - + # revision revision_info = is_seadoc_revision(file_uuid) @@ -863,7 +863,7 @@ def view_lib_file(request, repo_id, path): send_file_access_msg(request, repo, path, 'web') - return render(request, 'view_file_onlyoffice.html', onlyoffice_dict) + return render(request, 'onlyoffice_file_view_react.html', {**return_dict, **onlyoffice_dict}) else: return_dict['err'] = _('Error when prepare OnlyOffice file preview page.') @@ -1112,13 +1112,13 @@ def _download_file_from_share_link(request, fileshare, use_tmp_token=False): if not obj_id: messages.error(request, _('Unable to download file, wrong file path')) return HttpResponseRedirect(next_page) - + if use_tmp_token: dl_token = seafile_api.get_fileserver_access_token(repo.id, obj_id, 'download-link', fileshare.username, use_onetime=False) if not dl_token: messages.error(request, _('Unable to download file.')) - + return HttpResponseRedirect(gen_file_get_url(dl_token, filename)) return HttpResponseRedirect(gen_file_get_url_by_sharelink(fileshare.token))