diff --git a/apps/common/mobile/lib/controller/ContextMenu.jsx b/apps/common/mobile/lib/controller/ContextMenu.jsx index cc67da9c3..ff56b40d8 100644 --- a/apps/common/mobile/lib/controller/ContextMenu.jsx +++ b/apps/common/mobile/lib/controller/ContextMenu.jsx @@ -1,6 +1,7 @@ import React, { Component, Fragment } from 'react'; import { f7 } from 'framework7-react'; -import { Device } from '../../../../common/mobile/utils/device' +import {observer, inject} from "mobx-react"; +import { Device } from '../../../../common/mobile/utils/device'; import ContextMenuView, { idContextMenuElement, ActionsWithExtraItems } from '../view/ContextMenu'; @@ -23,6 +24,8 @@ class ContextMenuController extends Component { this.onDocumentReady = this.onDocumentReady.bind(this); this.onApiOpenContextMenu = this.onApiOpenContextMenu.bind(this); this.onApiHideContextMenu = this.onApiHideContextMenu.bind(this); + this.onApiShowForeignCursorLabel = this.onApiShowForeignCursorLabel.bind(this); + this.onApiHideForeignCursorLabel = this.onApiHideForeignCursorLabel.bind(this); } onDocumentReady() { @@ -38,6 +41,8 @@ class ContextMenuController extends Component { const api = Common.EditorApi.get(); api.asc_registerCallback('asc_onShowPopMenu', this.onApiOpenContextMenu); api.asc_registerCallback('asc_onHidePopMenu', this.onApiHideContextMenu); + api.asc_registerCallback('asc_onShowForeignCursorLabel', this.onApiShowForeignCursorLabel); + api.asc_registerCallback('asc_onHideForeignCursorLabel', this.onApiHideForeignCursorLabel); } offsetPopoverTop(popover) { @@ -150,12 +155,61 @@ class ContextMenuController extends Component { } } + onApiShowForeignCursorLabel(UserId, X, Y, color) { + /** coauthoring begin **/ + const tipHeight = 20; + + if (!this.fastCoAuthTips) { + this.fastCoAuthTips = []; + } + let src; + for (let i=0; i`); + src.attr('userid', UserId); + src.css({'background-color': '#'+Common.Utils.ThemeColor.getHexColor(color.get_r(), color.get_g(), color.get_b())}); + src.text(this.getUserName(UserId)); + $$('#id_main_parent').append(src); + this.fastCoAuthTips.push(src); + //src.fadeIn(150); + src[0].classList.add('active'); + + $$('#id_main_view').append(src); + } + src.css({ + top: (Y - tipHeight) + 'px', + left: X + 'px'}); + /** coauthoring end **/ + } + + onApiHideForeignCursorLabel(userId) { + /** coauthoring begin **/ + for (let i=0; i {src.remove()}); + src[0].classList.remove('active'); + src.remove(); + this.fastCoAuthTips.splice(i, 1); + break; + } + } + /** coauthoring end **/ + } + componentWillUnmount() { Common.Notifications.off('document:ready', this.onDocumentReady); const api = Common.EditorApi.get(); api.asc_unregisterCallback('asc_onShowPopMenu', this.onApiOpenContextMenu); api.asc_unregisterCallback('asc_onHidePopMenu', this.onApiHideContextMenu); + api.asc_unregisterCallback('asc_onShowForeignCursorLabel', this.onApiShowForeignCursorLabel); + api.asc_unregisterCallback('asc_onHideForeignCursorLabel', this.onApiHideForeignCursorLabel); } componentDidMount() { diff --git a/apps/common/mobile/lib/controller/collaboration/Collaboration.jsx b/apps/common/mobile/lib/controller/collaboration/Collaboration.jsx index 1229bc511..a72809b9b 100644 --- a/apps/common/mobile/lib/controller/collaboration/Collaboration.jsx +++ b/apps/common/mobile/lib/controller/collaboration/Collaboration.jsx @@ -1,17 +1,63 @@ import React, { Component } from 'react' import {observer, inject} from "mobx-react" +import { LocalStorage } from '../../../utils/LocalStorage'; class CollaborationController extends Component { constructor(props){ super(props); - Common.Notifications.on('configOptionsFill', () => { - const api = Common.EditorApi.get(); - // this.api = api; + Common.Notifications.on('engineCreated', (api) => { api.asc_registerCallback('asc_onAuthParticipantsChanged', this.onChangeEditUsers.bind(this)); api.asc_registerCallback('asc_onParticipantsChanged', this.onChangeEditUsers.bind(this)); api.asc_registerCallback('asc_onConnectionStateChanged', this.onUserConnection.bind(this)); + api.asc_registerCallback('asc_onCoAuthoringDisconnect', this.onCoAuthoringDisconnect.bind(this)); }); + + Common.Notifications.on('document:ready', this.onDocumentReady.bind(this)); + } + + onDocumentReady() { + const api = Common.EditorApi.get(); + const appOptions = this.props.storeAppOptions; + /** coauthoring begin **/ + let isFastCoauth; + if (appOptions.isEdit && appOptions.canLicense && !appOptions.isOffline && appOptions.canCoAuthoring) { + // Force ON fast co-authoring mode + isFastCoauth = true; + api.asc_SetFastCollaborative(isFastCoauth); + + if (window.editorType === 'de') { + const value = LocalStorage.getItem((isFastCoauth) ? "de-settings-showchanges-fast" : "de-settings-showchanges-strict"); + if (value !== null) { + api.SetCollaborativeMarksShowType( + value === 'all' ? Asc.c_oAscCollaborativeMarksShowType.All : + value === 'none' ? Asc.c_oAscCollaborativeMarksShowType.None : Asc.c_oAscCollaborativeMarksShowType.LastChanges); + } else { + api.SetCollaborativeMarksShowType(isFastCoauth ? Asc.c_oAscCollaborativeMarksShowType.None : Asc.c_oAscCollaborativeMarksShowType.LastChanges); + } + } + } else if (!appOptions.isEdit && appOptions.isRestrictedEdit) { + isFastCoauth = true; + api.asc_SetFastCollaborative(isFastCoauth); + window.editorType === 'de' && api.SetCollaborativeMarksShowType(Asc.c_oAscCollaborativeMarksShowType.None); + api.asc_setAutoSaveGap(1); + } else { + isFastCoauth = false; + api.asc_SetFastCollaborative(isFastCoauth); + window.editorType === 'de' && api.SetCollaborativeMarksShowType(Asc.c_oAscCollaborativeMarksShowType.None); + } + + if (appOptions.isEdit) { + let value; + if (window.editorType === 'sse') { + value = appOptions.canAutosave ? 1 : 0; // FORCE AUTOSAVE + } else { + value = isFastCoauth; // Common.localStorage.getItem("de-settings-autosave"); + value = (!isFastCoauth && value !== null) ? parseInt(value) : (appOptions.canCoAuthoring ? 1 : 0); + } + api.asc_setAutoSaveGap(value); + } + /** coauthoring end **/ } onChangeEditUsers(users) { @@ -24,6 +70,10 @@ class CollaborationController extends Component { this.props.users.connection(change); } + onCoAuthoringDisconnect() { + this.props.users.resetDisconnected(true); + } + render() { return null } diff --git a/apps/common/mobile/lib/controller/collaboration/Comments.jsx b/apps/common/mobile/lib/controller/collaboration/Comments.jsx index d7204f010..c0d087e62 100644 --- a/apps/common/mobile/lib/controller/collaboration/Comments.jsx +++ b/apps/common/mobile/lib/controller/collaboration/Comments.jsx @@ -238,6 +238,9 @@ class AddCommentController extends Component { } getUserInfo () { this.currentUser = this.props.users.currentUser; + if (!this.currentUser) { + this.currentUser = this.props.users.setCurrentUser(this.props.storeAppOptions.user.id); + } const name = this.currentUser.asc_getUserName(); return { name: name, diff --git a/apps/common/mobile/lib/store/users.js b/apps/common/mobile/lib/store/users.js index 4b4dc4eb3..bf334c8e2 100644 --- a/apps/common/mobile/lib/store/users.js +++ b/apps/common/mobile/lib/store/users.js @@ -8,12 +8,15 @@ export class storeUsers { reset: action, currentUser: observable, setCurrentUser: action, - connection: action + connection: action, + isDisconnected: observable, + resetDisconnected: action }) } users = []; currentUser; + isDisconnected = false; reset (users) { this.users = Object.values(users) @@ -25,6 +28,7 @@ export class storeUsers { this.currentUser = item; } }); + return this.currentUser; } connection (change) { @@ -41,6 +45,10 @@ export class storeUsers { !changed && change && (this.users[change.asc_getId()] = change); } + resetDisconnected (isDisconnected) { + this.isDisconnected = isDisconnected; + } + getInitials (name) { const fio = Common.Utils.UserInfoParser.getParsedName(name).split(' '); let initials = fio[0].substring(0, 1).toUpperCase(); @@ -62,4 +70,14 @@ export class storeUsers { }); return user; } + + searchUserByCurrentId (id) { + let user = null; + this.users.forEach((item) => { + if (item.asc_getId() === id) { + user = item; + } + }); + return user; + } } diff --git a/apps/common/mobile/lib/view/ContextMenu.jsx b/apps/common/mobile/lib/view/ContextMenu.jsx index 47c954162..264565226 100644 --- a/apps/common/mobile/lib/view/ContextMenu.jsx +++ b/apps/common/mobile/lib/view/ContextMenu.jsx @@ -47,7 +47,7 @@ const ActionsWithExtraItems = ({items, onMenuItemClick, opened, onActionClosed}) {items.length > 0 && items.map((item, index)=>{ return( - {onMenuItemClick(item.event)}}>{item.caption} + {onMenuItemClick(item.event)}}>{item.caption} ) })} diff --git a/apps/common/mobile/resources/less/common.less b/apps/common/mobile/resources/less/common.less index 8d2746af8..eeb8cdaa6 100644 --- a/apps/common/mobile/resources/less/common.less +++ b/apps/common/mobile/resources/less/common.less @@ -669,6 +669,22 @@ input[type="number"]::-webkit-inner-spin-button { } } +.username-tip { + height: 20px; + color: @white; + padding: 0 10px; + position: absolute; + z-index: 900; + display: none; + pointer-events: none; + transition: opacity 0.1ms ease-out; + opacity: 0; + &.active { + display: block; + opacity: 1; + } +} + diff --git a/apps/common/mobile/resources/less/material/comments.less b/apps/common/mobile/resources/less/material/comments.less index b1c72ccce..90a0f1cfe 100644 --- a/apps/common/mobile/resources/less/material/comments.less +++ b/apps/common/mobile/resources/less/material/comments.less @@ -39,7 +39,7 @@ } .comment-list { - .item-inner:after, li:last-child li .item-inner:after { + ul:after, .item-inner:after, li:last-child li .item-inner:after { content: none; } .comment-header { diff --git a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx index 1cd69fe13..14facac9e 100644 --- a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx +++ b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx @@ -11,7 +11,9 @@ import { Device } from '../../../../common/mobile/utils/device'; @inject ( stores => ({ isEdit: stores.storeAppOptions.isEdit, canViewComments: stores.storeAppOptions.canViewComments, - canReview: stores.storeAppOptions.canReview + canReview: stores.storeAppOptions.canReview, + users: stores.users, + isDisconnected: stores.users.isDisconnected })) class ContextMenu extends ContextMenuController { constructor(props) { @@ -21,12 +23,18 @@ class ContextMenu extends ContextMenuController { this.onApiShowComment = this.onApiShowComment.bind(this); this.onApiHideComment = this.onApiHideComment.bind(this); this.onApiShowChange = this.onApiShowChange.bind(this); + this.getUserName = this.getUserName.bind(this); } static closeContextMenu() { f7.popover.close(idContextMenuElement, false); } + getUserName(id) { + const user = this.props.users.searchUserByCurrentId(id); + return Common.Utils.UserInfoParser.getParsedName(user.asc_getUserName()); + } + componentWillUnmount() { super.componentWillUnmount(); @@ -224,7 +232,7 @@ class ContextMenu extends ContextMenuController { const { t } = this.props; const _t = t("ContextMenu", { returnObjects: true }); - const { isEdit, canViewComments, canReview } = this.props; + const { isEdit, canViewComments, canReview, isDisconnected } = this.props; const api = Common.EditorApi.get(); const stack = api.getSelectedElements(); @@ -294,7 +302,7 @@ class ContextMenu extends ContextMenuController { items[indexAfter] = items.splice(indexBefore, 1, items[indexAfter])[0]; }; - if ( isEdit && !this.isDisconnected ) { + if ( isEdit && !isDisconnected ) { if ( !lockedText && !lockedTable && !lockedImage && !lockedHeader && canCopy ) { itemsIcon.push({ event: 'cut', diff --git a/apps/presentationeditor/mobile/locale/en.json b/apps/presentationeditor/mobile/locale/en.json index 15ec8dca9..6d6abedc4 100644 --- a/apps/presentationeditor/mobile/locale/en.json +++ b/apps/presentationeditor/mobile/locale/en.json @@ -24,6 +24,19 @@ } } }, + "ContextMenu": { + "menuViewComment": "View Comment", + "menuAddComment": "Add Comment", + "menuDelete": "Delete", + "menuEdit": "Edit", + "menuAddLink": "Add Link", + "menuOpenLink": "Open Link", + "menuMore": "More", + "menuCancel": "Cancel", + "textCopyCutPasteActions": "Copy, Cut and Paste Actions", + "errorCopyCutPaste": "Copy, cut and paste actions using the context menu will be performed within the current file only.", + "textDoNotShowAgain": "Don't show again" + }, "View": { "Settings": { "textDone": "Done", @@ -101,6 +114,7 @@ "textColumns": "Columns", "textRows": "Rows", "textCancel": "Cancel", + "textAddLink": "Add Link", "textLink": "Link", "textLinkType": "Link Type", "textExternalLink": "External Link", @@ -267,6 +281,27 @@ "textThemeColors": "Theme Colors", "textStandartColors": "Standard Colors", "textCustomColors": "Custom Colors" + }, + "Collaboration": { + "textCollaboration": "Collaboration", + "textBack": "Back", + "textUsers": "Users", + "textEditUser": "Users who are editing the file:", + "textComments": "Comments", + "textAddComment": "Add Comment", + "textCancel": "Cancel", + "textDone": "Done", + "textNoComments": "This document doesn't contain comments", + "textEdit": "Edit", + "textResolve": "Resolve", + "textReopen": "Reopen", + "textAddReply": "Add Reply", + "textDeleteComment": "Delete Comment", + "textMessageDeleteComment": "Do you really want to delete this comment?", + "textMessageDeleteReply": "Do you really want to delete this reply?", + "textDeleteReply": "Delete Reply", + "textEditComment": "Edit Comment", + "textEditReply": "Edit Reply" } } } \ No newline at end of file diff --git a/apps/presentationeditor/mobile/src/app.js b/apps/presentationeditor/mobile/src/app.js index 3ea2811b7..0f945afb1 100644 --- a/apps/presentationeditor/mobile/src/app.js +++ b/apps/presentationeditor/mobile/src/app.js @@ -4,6 +4,8 @@ import ReactDOM from 'react-dom'; // Import Framework7 import Framework7 from 'framework7/lite-bundle'; +import { Dom7 } from 'framework7'; +window.$$ = Dom7; // Import Framework7-React Plugin import Framework7React from 'framework7-react'; diff --git a/apps/presentationeditor/mobile/src/controller/ContextMenu.jsx b/apps/presentationeditor/mobile/src/controller/ContextMenu.jsx new file mode 100644 index 000000000..6e4c92d74 --- /dev/null +++ b/apps/presentationeditor/mobile/src/controller/ContextMenu.jsx @@ -0,0 +1,312 @@ +import React, { useContext } from 'react'; +import { f7 } from 'framework7-react'; +import { inject, observer } from "mobx-react"; +import { withTranslation} from 'react-i18next'; +import { LocalStorage } from '../../../../common/mobile/utils/LocalStorage'; + +import ContextMenuController from '../../../../common/mobile/lib/controller/ContextMenu'; +import { idContextMenuElement } from '../../../../common/mobile/lib/view/ContextMenu'; +import { Device } from '../../../../common/mobile/utils/device'; + +@inject ( stores => ({ + isEdit: stores.storeAppOptions.isEdit, + canViewComments: stores.storeAppOptions.canViewComments, + users: stores.users, + isDisconnected: stores.users.isDisconnected +})) +class ContextMenu extends ContextMenuController { + constructor(props) { + super(props); + + // console.log('context menu controller created'); + this.onApiShowComment = this.onApiShowComment.bind(this); + this.onApiHideComment = this.onApiHideComment.bind(this); + this.getUserName = this.getUserName.bind(this); + } + + static closeContextMenu() { + f7.popover.close(idContextMenuElement, false); + } + + getUserName(id) { + const user = this.props.users.searchUserByCurrentId(id); + return Common.Utils.UserInfoParser.getParsedName(user.asc_getUserName()); + } + + componentWillUnmount() { + super.componentWillUnmount(); + + const api = Common.EditorApi.get(); + api.asc_unregisterCallback('asc_onShowComment', this.onApiShowComment); + api.asc_unregisterCallback('asc_onHideComment', this.onApiHideComment); + } + + + onApiShowComment(comments) { + this.isComments = comments && comments.length > 0; + } + + onApiHideComment() { + this.isComments = false; + } + + // onMenuClosed() { + // super.onMenuClosed(); + // } + + onMenuItemClick(action) { + super.onMenuItemClick(action); + + const api = Common.EditorApi.get(); + switch (action) { + case 'cut': + if (!api.Cut() && !LocalStorage.getBool("pe-hide-copy-cut-paste-warning")) { + this.showCopyCutPasteModal(); + } + break; + case 'copy': + if (!api.Copy() && !LocalStorage.getBool("pe-hide-copy-cut-paste-warning")) { + this.showCopyCutPasteModal(); + } + break; + case 'paste': + if (!api.Paste() && !LocalStorage.getBool("pe-hide-copy-cut-paste-warning")) { + this.showCopyCutPasteModal(); + } + break; + case 'addcomment': + Common.Notifications.trigger('addcomment'); + break; + case 'viewcomment': + Common.Notifications.trigger('viewcomment'); + break; + case 'delete': + api.asc_Remove(); + break; + case 'edit': + setTimeout(() => { + this.props.openOptions('edit'); + }, 0); + break; + case 'addlink': + setTimeout(() => { + this.props.openOptions('add', 'link'); + }, 400) + break; + case 'openlink': + const stack = Common.EditorApi.get().getSelectedElements(); + let value; + stack.forEach((item) => { + if (item.get_ObjectType() == Asc.c_oAscTypeSelectElement.Hyperlink) { + value = item.get_ObjectValue().get_Value(); + } + }); + value && this.openLink(value); + break; + } + + console.log("click context menu item: " + action); + } + + showCopyCutPasteModal() { + const { t } = this.props; + const _t = t("ContextMenu", { returnObjects: true }); + f7.dialog.create({ + title: _t.textCopyCutPasteActions, + text: _t.errorCopyCutPaste, + content: `
+ + ${_t.textDoNotShowAgain} +
`, + buttons: [{ + text: 'OK', + onClick: () => { + const dontShow = $$('input[name="checkbox-show"]').prop('checked'); + if (dontShow) LocalStorage.setItem("de-hide-copy-cut-paste-warning", 1); + } + }] + }).open(); + } + + openLink(url) { + const api = Common.EditorApi.get(); + if (api.asc_getUrlType(url) > 0) { + const newDocumentPage = window.open(url, '_blank'); + if (newDocumentPage) { + newDocumentPage.focus(); + } + } else { + api.asc_GoToInternalHyperlink(url); + } + } + + onDocumentReady() { + super.onDocumentReady(); + + const api = Common.EditorApi.get(); + api.asc_registerCallback('asc_onShowComment', this.onApiShowComment); + api.asc_registerCallback('asc_onHideComment', this.onApiHideComment); + } + + initMenuItems() { + if ( !Common.EditorApi ) return []; + + const { t } = this.props; + const _t = t("ContextMenu", { returnObjects: true }); + + const { isEdit, canViewComments, canReview, isDisconnected } = this.props; + + const api = Common.EditorApi.get(); + const stack = api.getSelectedElements(); + const canCopy = api.can_CopyCut(); + + let itemsIcon = [], + itemsText = []; + + let isText = false, + isTable = false, + isImage = false, + isChart = false, + isShape = false, + isLink = false, + isSlide = false, + isObject = false; + + stack.forEach(item => { + const objectType = item.get_ObjectType(), + objectValue = item.get_ObjectValue(); + + if (objectType == Asc.c_oAscTypeSelectElement.Paragraph) { + isText = true; + } else if (objectType == Asc.c_oAscTypeSelectElement.Image) { + isImage = true; + } else if (objectType == Asc.c_oAscTypeSelectElement.Chart) { + isChart = true; + } else if (objectType == Asc.c_oAscTypeSelectElement.Shape) { + isShape = true; + } else if (objectType == Asc.c_oAscTypeSelectElement.Table) { + isTable = true; + } else if (objectType == Asc.c_oAscTypeSelectElement.Hyperlink) { + isLink = true; + } else if (objectType == Asc.c_oAscTypeSelectElement.Slide) { + isSlide = true; + } + }); + + isObject = isText || isImage || isChart || isShape || isTable; + + if (canCopy && isObject) { + itemsIcon.push({ + event: 'copy', + icon: 'icon-copy' + }); + } + if (canViewComments && this.isComments && !isEdit) { + itemsText.push({ + caption: _t.menuViewComment, + event: 'viewcomment' + }); + } + + if ( stack.length > 0 ) { + let topObject = stack[stack.length - 1], + topObjectType = topObject.get_ObjectType(), + topObjectValue = topObject.get_ObjectValue(), + objectLocked = typeof topObjectValue.get_Locked === 'function' ? topObjectValue.get_Locked() : false; + + !objectLocked && (objectLocked = typeof topObjectValue.get_LockDelete === 'function' ? topObjectValue.get_LockDelete() : false); + + const swapItems = function(items, indexBefore, indexAfter) { + items[indexAfter] = items.splice(indexBefore, 1, items[indexAfter])[0]; + }; + + if (!objectLocked && isEdit && !isDisconnected) { + if (canCopy && isObject) { + itemsIcon.push({ + event: 'cut', + icon: 'icon-cut' + }); + + // Swap 'Copy' and 'Cut' + swapItems(itemsIcon, 0, 1); + } + + itemsIcon.push({ + event: 'paste', + icon: 'icon-paste' + }); + + if (isObject) + itemsText.push({ + caption: _t.menuDelete, + event: 'delete' + }); + + itemsText.push({ + caption: _t.menuEdit, + event: 'edit' + }); + + if (!isLink && api.can_AddHyperlink() !== false) { + itemsText.push({ + caption: _t.menuAddLink, + event: 'addlink' + }); + } + + if (this.isComments && canViewComments) { + itemsText.push({ + caption: _t.menuViewComment, + event: 'viewcomment' + }); + } + + var hideAddComment = (isText && isChart) || api.can_AddQuotedComment() === false || !canViewComments; + if (!hideAddComment) { + itemsText.push({ + caption: _t.menuAddComment, + event: 'addcomment' + }); + } + } + } + + if (isLink) { + itemsText.push({ + caption: _t.menuOpenLink, + event: 'openlink' + }); + } + + + if ( Device.phone && itemsText.length > 2 ) { + this.extraItems = itemsText.splice(2,itemsText.length, { + caption: _t.menuMore, + event: 'showActionSheet' + }); + } + + return itemsIcon.concat(itemsText); + // return [{ + // caption: 'Edit', + // event: 'edit' + // }, { + // caption: 'View', + // event: 'view' + // }, { + // icon: 'icon-paste', + // event: 'review' + // }]; + } + + initExtraItems () { + return (this.extraItems && this.extraItems.length > 0 ? this.extraItems : []); + } +} + +const _ContextMenu = withTranslation()(ContextMenu); +_ContextMenu.closeContextMenu = ContextMenu.closeContextMenu; +export { _ContextMenu as default }; \ No newline at end of file diff --git a/apps/presentationeditor/mobile/src/controller/Main.jsx b/apps/presentationeditor/mobile/src/controller/Main.jsx index d8ee673da..8b75c19fa 100644 --- a/apps/presentationeditor/mobile/src/controller/Main.jsx +++ b/apps/presentationeditor/mobile/src/controller/Main.jsx @@ -1,9 +1,15 @@ -import React, { Component } from 'react' +import React, { Component, Fragment } from 'react' import { inject } from "mobx-react"; import { f7 } from "framework7-react"; import { withTranslation } from 'react-i18next'; -import CollaborationController from '../../../../common/mobile/lib/controller/collaboration/Collaboration.jsx' +import CollaborationController from '../../../../common/mobile/lib/controller/collaboration/Collaboration.jsx'; +import { + CommentsController, + AddCommentController, + EditCommentController, + ViewCommentsController +} from "../../../../common/mobile/lib/controller/collaboration/Comments"; @inject("storeFocusObjects", "storeAppOptions", "storePresentationInfo", "storePresentationSettings", "storeSlideSettings", "storeTextSettings", "storeTableSettings", "storeChartSettings", "storeLinkSettings") class MainController extends Component { @@ -334,6 +340,8 @@ class MainController extends Component { Common.Gateway.documentReady(); f7.emit('resize'); + + Common.Notifications.trigger('document:ready'); } _onOpenDocumentProgress(progress) { @@ -346,7 +354,15 @@ class MainController extends Component { } render() { - return + return ( + + + + + + + + ) } componentDidMount() { diff --git a/apps/presentationeditor/mobile/src/controller/add/AddLink.jsx b/apps/presentationeditor/mobile/src/controller/add/AddLink.jsx index e9e9ba81a..609372460 100644 --- a/apps/presentationeditor/mobile/src/controller/add/AddLink.jsx +++ b/apps/presentationeditor/mobile/src/controller/add/AddLink.jsx @@ -102,6 +102,7 @@ class AddLinkController extends Component { return ( ) } diff --git a/apps/presentationeditor/mobile/src/controller/add/AddOther.jsx b/apps/presentationeditor/mobile/src/controller/add/AddOther.jsx index e2838493e..4ac82cca4 100644 --- a/apps/presentationeditor/mobile/src/controller/add/AddOther.jsx +++ b/apps/presentationeditor/mobile/src/controller/add/AddOther.jsx @@ -88,10 +88,38 @@ class AddOtherController extends Component { }); } + hideAddComment () { + const api = Common.EditorApi.get(); + const stack = api.getSelectedElements(); + let isText = false, + isChart = false; + + stack.forEach((item) => { + const objectType = item.get_ObjectType(); + if (objectType === Asc.c_oAscTypeSelectElement.Paragraph) { + isText = true; + } else if (objectType === Asc.c_oAscTypeSelectElement.Chart) { + isChart = true; + } + }); + if (stack.length > 0) { + const topObject = stack[stack.length - 1]; + const topObjectValue = topObject.get_ObjectValue(); + let objectLocked = typeof topObjectValue.get_Locked === 'function' ? topObjectValue.get_Locked() : false; + !objectLocked && (objectLocked = typeof topObjectValue.get_LockDelete === 'function' ? topObjectValue.get_LockDelete() : false); + if (!objectLocked) { + return ((isText && isChart) || api.can_AddQuotedComment() === false); + } + } + return true; + } + render () { return ( - ) } diff --git a/apps/presentationeditor/mobile/src/less/app.less b/apps/presentationeditor/mobile/src/less/app.less index 4297bfb61..47f58eb33 100644 --- a/apps/presentationeditor/mobile/src/less/app.less +++ b/apps/presentationeditor/mobile/src/less/app.less @@ -11,6 +11,8 @@ @import '../../../../common/mobile/resources/less/dataview.less'; @import '../../../../common/mobile/resources/less/about.less'; @import '../../../../common/mobile/resources/less/search.less'; +@import '../../../../common/mobile/resources/less/contextmenu.less'; +@import '../../../../common/mobile/resources/less/comments.less'; @import './app-material.less'; @import './app-ios.less'; @import './icons-ios.less'; diff --git a/apps/presentationeditor/mobile/src/page/main.jsx b/apps/presentationeditor/mobile/src/page/main.jsx index e1c094826..3a3d2f4cf 100644 --- a/apps/presentationeditor/mobile/src/page/main.jsx +++ b/apps/presentationeditor/mobile/src/page/main.jsx @@ -7,6 +7,7 @@ import Settings from '../view/settings/Settings'; import CollaborationView from '../../../../common/mobile/lib/view/collaboration/Collaboration.jsx'; import { Device } from '../../../../common/mobile/utils/device'; import { Search, SearchSettings } from '../controller/Search'; +import ContextMenu from '../controller/ContextMenu'; export default class MainPage extends Component { constructor(props) { @@ -19,12 +20,17 @@ export default class MainPage extends Component { }; } - handleClickToOpenOptions = opts => { + handleClickToOpenOptions = (opts, showOpts) => { + ContextMenu.closeContextMenu(); + this.setState(state => { if ( opts == 'edit' ) return {editOptionsVisible: true}; else if ( opts == 'add' ) - return {addOptionsVisible: true}; + return { + addOptionsVisible: true, + addShowOptions: showOpts + }; else if ( opts == 'settings' ) return {settingsVisible: true}; else if ( opts == 'coauth' ) @@ -77,7 +83,7 @@ export default class MainPage extends Component { } { !this.state.addOptionsVisible ? null : - + } { !this.state.settingsVisible ? null : @@ -87,6 +93,7 @@ export default class MainPage extends Component { !this.state.collaborationVisible ? null : } + ) } diff --git a/apps/presentationeditor/mobile/src/store/mainStore.js b/apps/presentationeditor/mobile/src/store/mainStore.js index 4ed3fd33e..299a6a63b 100644 --- a/apps/presentationeditor/mobile/src/store/mainStore.js +++ b/apps/presentationeditor/mobile/src/store/mainStore.js @@ -16,6 +16,7 @@ import { storeLinkSettings } from "./linkSettings"; // import {storeParagraphSettings} from "./paragraphSettings"; // import {storeShapeSettings} from "./shapeSettings"; // import {storeImageSettings} from "./imageSettings"; +import {storeComments} from "../../../../common/mobile/lib/store/comments"; export const stores = { storeAppOptions: new storeAppOptions(), @@ -31,10 +32,11 @@ export const stores = { storeShapeSettings: new storeShapeSettings(), storeTableSettings: new storeTableSettings(), storeChartSettings: new storeChartSettings(), - storeLinkSettings: new storeLinkSettings() + storeLinkSettings: new storeLinkSettings(), // storeTextSettings: new storeTextSettings(), // storeParagraphSettings: new storeParagraphSettings(), // storeShapeSettings: new storeShapeSettings(), // storeChartSettings: new storeChartSettings(), + storeComments: new storeComments() }; diff --git a/apps/presentationeditor/mobile/src/view/add/Add.jsx b/apps/presentationeditor/mobile/src/view/add/Add.jsx index 00f312662..d9483113f 100644 --- a/apps/presentationeditor/mobile/src/view/add/Add.jsx +++ b/apps/presentationeditor/mobile/src/view/add/Add.jsx @@ -1,5 +1,5 @@ import React, {Component, useEffect} from 'react'; -import {View,Page,Navbar,NavRight,Link,Popup,Popover,Icon,Tabs,Tab} from 'framework7-react'; +import {View,Page,Navbar,NavRight, NavTitle, Link,Popup,Popover,Icon,Tabs,Tab} from 'framework7-react'; import { useTranslation } from 'react-i18next'; import {f7} from 'framework7-react'; import { observer, inject } from "mobx-react"; @@ -43,13 +43,16 @@ const AddLayoutNavbar = ({ tabs, inPopover }) => { const isAndroid = Device.android; return ( -
- {tabs.map((item, index) => - - - )} - {isAndroid && } -
+ {tabs.length > 1 ? +
+ {tabs.map((item, index) => + + + )} + {isAndroid && } +
: + {tabs[0].caption} + } { !inPopover && }
) @@ -69,32 +72,42 @@ const AddLayoutContent = ({ tabs }) => { const AddTabs = props => { const { t } = useTranslation(); - const _t = t('Add', {returnObjects: true}); + const _t = t('View.Add', {returnObjects: true}); + const showPanels = props.showPanels; const tabs = []; - tabs.push({ - caption: _t.textSlide, - id: 'add-slide', - icon: 'icon-add-slide', - component: - }); - tabs.push({ - caption: _t.textShape, - id: 'add-shape', - icon: 'icon-add-shape', - component: - }); - tabs.push({ - caption: _t.textImage, - id: 'add-image', - icon: 'icon-add-image', - component: - }); - tabs.push({ - caption: _t.textOther, - id: 'add-other', - icon: 'icon-add-other', - component: - }); + if (!showPanels) { + tabs.push({ + caption: _t.textSlide, + id: 'add-slide', + icon: 'icon-add-slide', + component: + }); + tabs.push({ + caption: _t.textShape, + id: 'add-shape', + icon: 'icon-add-shape', + component: + }); + tabs.push({ + caption: _t.textImage, + id: 'add-image', + icon: 'icon-add-image', + component: + }); + tabs.push({ + caption: _t.textOther, + id: 'add-other', + icon: 'icon-add-other', + component: + }); + } + if (showPanels && showPanels === 'link') { + tabs.push({ + caption: _t.textAddLink, + id: 'add-link', + component: + }); + } return ( @@ -119,10 +132,10 @@ class AddView extends Component { return ( show_popover ? this.props.onclosed()}> - + : this.props.onclosed()}> - + ) } @@ -142,7 +155,7 @@ const Add = props => { if ( props.onclosed ) props.onclosed(); }; - return + return }; export default Add; \ No newline at end of file diff --git a/apps/presentationeditor/mobile/src/view/add/AddLink.jsx b/apps/presentationeditor/mobile/src/view/add/AddLink.jsx index 567a23e84..621df428c 100644 --- a/apps/presentationeditor/mobile/src/view/add/AddLink.jsx +++ b/apps/presentationeditor/mobile/src/view/add/AddLink.jsx @@ -103,7 +103,7 @@ const PageLink = props => { return ( - + {!props.noNavbar && } { const { t } = useTranslation(); const _t = t('View.Add', {returnObjects: true}); const showInsertLink = props.storeLinkSettings.canAddLink && !props.storeFocusObjects.paragraphLocked; + const hideAddComment = props.hideAddComment(); return ( { }}> - + {!hideAddComment && { + props.closeModal(); + Common.Notifications.trigger('addcomment'); + }}> - + } {showInsertLink && diff --git a/apps/spreadsheeteditor/mobile/src/app.js b/apps/spreadsheeteditor/mobile/src/app.js index 8d55619f5..ab1e87de8 100644 --- a/apps/spreadsheeteditor/mobile/src/app.js +++ b/apps/spreadsheeteditor/mobile/src/app.js @@ -4,6 +4,8 @@ import ReactDOM from 'react-dom'; // Import Framework7 import Framework7 from 'framework7/lite-bundle'; +import { Dom7 } from 'framework7'; +window.$$ = Dom7; // Import Framework7-React Plugin import Framework7React from 'framework7-react'; diff --git a/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx b/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx index 81f22e0ed..e1950d919 100644 --- a/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx +++ b/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; +import { f7 } from 'framework7-react'; import StatusbarView from '../view/Statusbar'; import { inject } from 'mobx-react'; @@ -7,12 +8,25 @@ const Statusbar = inject('sheets')(props => { const {sheets} = props; useEffect(() => { - console.log("status bar did mount"); + const on_api_created = api => { + api.asc_registerCallback('asc_onSheetsChanged', onApiSheetsChanged.bind(api)); + }; + + const on_main_view_click = e => { + // f7.popover.close('.document-menu.modal-in'); + }; Common.Notifications.on('document:ready', onApiSheetsChanged); - Common.Notifications.on('engineCreated', api => { - api.asc_registerCallback('asc_onSheetsChanged', onApiSheetsChanged.bind(api)); - }); + Common.Notifications.on('engineCreated', on_api_created); + + $$('.view-main').on('click', on_main_view_click); + + return () => { + Common.Notifications.off('document:ready', onApiSheetsChanged); + Common.Notifications.off('engineCreated', on_api_created); + + $$('.view-main').off('click', on_main_view_click); + }; }, []); const onApiSheetsChanged = api => { diff --git a/vendor/framework7-react/build/webpack.config.js b/vendor/framework7-react/build/webpack.config.js index 8d4fc4a43..ae3ee3a13 100644 --- a/vendor/framework7-react/build/webpack.config.js +++ b/vendor/framework7-react/build/webpack.config.js @@ -4,7 +4,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const WorkboxPlugin = require('workbox-webpack-plugin'); @@ -53,6 +53,7 @@ module.exports = { minimizer: [new TerserPlugin({ sourceMap: true, })], + moduleIds: 'named', }, module: { rules: [ @@ -162,8 +163,8 @@ module.exports = { }), ...(env === 'production' ? [ - new OptimizeCSSPlugin({ - cssProcessorOptions: { + new CssMinimizerPlugin({ + processorOptions: { safe: true, map: { inline: false }, }, @@ -172,7 +173,7 @@ module.exports = { ] : [ // Development only plugins new webpack.HotModuleReplacementPlugin(), - new webpack.NamedModulesPlugin(), + // new webpack.NamedModulesPlugin(), ]), // new CleanWebpackPlugin(), new HtmlWebpackPlugin({ diff --git a/vendor/framework7-react/package.json b/vendor/framework7-react/package.json index a7e241a4d..4f10dcfef 100644 --- a/vendor/framework7-react/package.json +++ b/vendor/framework7-react/package.json @@ -53,27 +53,24 @@ "cpy-cli": "^3.1.1", "cross-env": "^7.0.3", "css-loader": "^4.3.0", + "css-minimizer-webpack-plugin": "^1.3.0", "file-loader": "^6.2.0", - "html-webpack-plugin": "^4.5.1", + "html-webpack-plugin": "^5.3.1", "less": "^3.13.1", "less-loader": "^6.2.0", - "mini-css-extract-plugin": "^0.9.0", + "mini-css-extract-plugin": "^1.3.9", "mobx": "^6.1.8", "mobx-react": "^7.1.0", - "optimize-css-assets-webpack-plugin": "^5.0.4", "ora": "^4.1.1", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", - "react-redux": "^7.2.2", - "redux": "^4.0.5", - "redux-thunk": "^2.3.0", "rimraf": "^3.0.2", "style-loader": "^1.3.0", "terser-webpack-plugin": "^3.1.0", "url-loader": "^4.1.1", - "webpack": "^4.46.0", - "webpack-cli": "^3.3.12", + "webpack": "^5.26.3", + "webpack-cli": "^4.5.0", "webpack-dev-server": "^3.11.2", - "workbox-webpack-plugin": "^5.1.4" + "workbox-webpack-plugin": "^6.1.2" } }