From 4b0ec3e352b2ba5dacf698f50563d73aab058191 Mon Sep 17 00:00:00 2001 From: JuliaSvinareva Date: Tue, 23 Mar 2021 14:05:45 +0300 Subject: [PATCH] [PE mobile] Add context menu --- apps/common/mobile/lib/view/ContextMenu.jsx | 2 +- .../resources/less/material/comments.less | 2 +- apps/presentationeditor/mobile/locale/en.json | 14 + apps/presentationeditor/mobile/src/app.js | 2 + .../mobile/src/controller/ContextMenu.jsx | 312 ++++++++++++++++++ .../mobile/src/controller/Main.jsx | 2 + .../mobile/src/controller/add/AddLink.jsx | 1 + .../mobile/src/less/app.less | 2 + .../mobile/src/page/main.jsx | 13 +- .../mobile/src/view/add/Add.jsx | 85 +++-- .../mobile/src/view/add/AddLink.jsx | 2 +- 11 files changed, 395 insertions(+), 42 deletions(-) create mode 100644 apps/presentationeditor/mobile/src/controller/ContextMenu.jsx 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/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/presentationeditor/mobile/locale/en.json b/apps/presentationeditor/mobile/locale/en.json index dc57ad180..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", 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 76f2f65ce..8b75c19fa 100644 --- a/apps/presentationeditor/mobile/src/controller/Main.jsx +++ b/apps/presentationeditor/mobile/src/controller/Main.jsx @@ -340,6 +340,8 @@ class MainController extends Component { Common.Gateway.documentReady(); f7.emit('resize'); + + Common.Notifications.trigger('document:ready'); } _onOpenDocumentProgress(progress) { 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/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/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 && }