[PE mobile] Add context menu

This commit is contained in:
JuliaSvinareva 2021-03-23 14:05:45 +03:00
parent cb755596ff
commit 4b0ec3e352
11 changed files with 395 additions and 42 deletions

View file

@ -47,7 +47,7 @@ const ActionsWithExtraItems = ({items, onMenuItemClick, opened, onActionClosed})
<ActionsGroup>
{items.length > 0 && items.map((item, index)=>{
return(
<ActionsButton key={`act-${item.caption}`} onClick={() => {onMenuItemClick(item.event)}}>{item.caption}</ActionsButton>
<ActionsButton key={`act-${index}`} onClick={() => {onMenuItemClick(item.event)}}>{item.caption}</ActionsButton>
)
})}
</ActionsGroup>

View file

@ -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 {

View file

@ -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",

View file

@ -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';

View file

@ -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: `<div class="checkbox-in-modal">
<label class="checkbox">
<input type="checkbox" name="checkbox-show" />
<i class="icon-checkbox"></i>
</label>
<span class="right-text">${_t.textDoNotShowAgain}</span>
</div>`,
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 };

View file

@ -340,6 +340,8 @@ class MainController extends Component {
Common.Gateway.documentReady();
f7.emit('resize');
Common.Notifications.trigger('document:ready');
}
_onOpenDocumentProgress(progress) {

View file

@ -102,6 +102,7 @@ class AddLinkController extends Component {
return (
<PageLink onInsertLink={this.onInsertLink}
getTextDisplay={this.getTextDisplay}
noNavbar={this.props.noNavbar}
/>
)
}

View file

@ -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';

View file

@ -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 :
<AddOptions onclosed={this.handleOptionsViewClosed.bind(this, 'add')} />
<AddOptions onclosed={this.handleOptionsViewClosed.bind(this, 'add')} showOptions={this.state.addShowOptions} />
}
{
!this.state.settingsVisible ? null :
@ -87,6 +93,7 @@ export default class MainPage extends Component {
!this.state.collaborationVisible ? null :
<CollaborationView onclosed={this.handleOptionsViewClosed.bind(this, 'coauth')} />
}
<ContextMenu openOptions={this.handleClickToOpenOptions.bind(this)} />
</Page>
)
}

View file

@ -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 (
<Navbar>
<div className='tab-buttons tabbar'>
{tabs.map((item, index) =>
<Link key={"pe-link-" + item.id} tabLink={"#" + item.id} tabLinkActive={index === 0}>
<Icon slot="media" icon={item.icon}></Icon>
</Link>)}
{isAndroid && <span className='tab-link-highlight' style={{width: 100 / tabs.lenght + '%'}}></span>}
</div>
{tabs.length > 1 ?
<div className='tab-buttons tabbar'>
{tabs.map((item, index) =>
<Link key={"pe-link-" + item.id} tabLink={"#" + item.id} tabLinkActive={index === 0}>
<Icon slot="media" icon={item.icon}></Icon>
</Link>)}
{isAndroid && <span className='tab-link-highlight' style={{width: 100 / tabs.lenght + '%'}}></span>}
</div> :
<NavTitle>{tabs[0].caption}</NavTitle>
}
{ !inPopover && <NavRight><Link icon='icon-expand-down' popupClose=".add-popup"></Link></NavRight> }
</Navbar>
)
@ -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: <AddSlideController />
});
tabs.push({
caption: _t.textShape,
id: 'add-shape',
icon: 'icon-add-shape',
component: <AddShapeController />
});
tabs.push({
caption: _t.textImage,
id: 'add-image',
icon: 'icon-add-image',
component: <AddImageController />
});
tabs.push({
caption: _t.textOther,
id: 'add-other',
icon: 'icon-add-other',
component: <AddOtherController />
});
if (!showPanels) {
tabs.push({
caption: _t.textSlide,
id: 'add-slide',
icon: 'icon-add-slide',
component: <AddSlideController />
});
tabs.push({
caption: _t.textShape,
id: 'add-shape',
icon: 'icon-add-shape',
component: <AddShapeController/>
});
tabs.push({
caption: _t.textImage,
id: 'add-image',
icon: 'icon-add-image',
component: <AddImageController/>
});
tabs.push({
caption: _t.textOther,
id: 'add-other',
icon: 'icon-add-other',
component: <AddOtherController/>
});
}
if (showPanels && showPanels === 'link') {
tabs.push({
caption: _t.textAddLink,
id: 'add-link',
component: <AddLinkController noNavbar={true}/>
});
}
return (
<View style={props.style} stackPages={true} routes={routes}>
<Page pageContent={false}>
@ -119,10 +132,10 @@ class AddView extends Component {
return (
show_popover ?
<Popover id="add-popover" className="popover__titled" onPopoverClosed={() => this.props.onclosed()}>
<AddTabs inPopover={true} onOptionClick={this.onoptionclick} style={{height: '410px'}} />
<AddTabs inPopover={true} onOptionClick={this.onoptionclick} style={{height: '410px'}} showPanels={this.props.showPanels} />
</Popover> :
<Popup className="add-popup" onPopupClosed={() => this.props.onclosed()}>
<AddTabs onOptionClick={this.onoptionclick} />
<AddTabs onOptionClick={this.onoptionclick} showPanels={this.props.showPanels} />
</Popup>
)
}
@ -142,7 +155,7 @@ const Add = props => {
if ( props.onclosed )
props.onclosed();
};
return <AddView usePopover={!Device.phone} onclosed={onviewclosed} />
return <AddView usePopover={!Device.phone} onclosed={onviewclosed} showPanels={props.showOptions} />
};
export default Add;

View file

@ -103,7 +103,7 @@ const PageLink = props => {
return (
<Page>
<Navbar title={_t.textLink} backLink={_t.textBack}/>
{!props.noNavbar && <Navbar title={_t.textLink} backLink={_t.textBack}/>}
<List inlineLabels className='inputs-list'>
<ListItem link={'/add-link-type/'} title={_t.textLinkType} after={textType} routeProps={{
changeType: changeType,