From 89d297ea9c2b766c5066b9f5488745342d9c6f6e Mon Sep 17 00:00:00 2001 From: Maxim Kadushkin Date: Wed, 10 Feb 2021 00:01:42 +0300 Subject: [PATCH] [DE mobile] context menu moved to common --- .../mobile/lib/controller/ContextMenu.jsx | 157 ++++++++++++++++++ apps/common/mobile/lib/view/ContextMenu.jsx | 39 +++++ .../mobile/resources/less/contextmenu.less | 7 + .../resources/less/ios/contextmenu.less | 102 ++++++++++++ .../resources/less/material/contextmenu.less | 32 ++++ .../mobile/src/controller/ContextMenu.jsx | 88 +++------- apps/documenteditor/mobile/src/less/app.less | 2 + apps/documenteditor/mobile/src/page/main.jsx | 2 +- .../mobile/src/view/ContextMenu.jsx | 21 +-- 9 files changed, 371 insertions(+), 79 deletions(-) create mode 100644 apps/common/mobile/lib/controller/ContextMenu.jsx create mode 100644 apps/common/mobile/lib/view/ContextMenu.jsx create mode 100644 apps/common/mobile/resources/less/contextmenu.less create mode 100644 apps/common/mobile/resources/less/ios/contextmenu.less create mode 100644 apps/common/mobile/resources/less/material/contextmenu.less diff --git a/apps/common/mobile/lib/controller/ContextMenu.jsx b/apps/common/mobile/lib/controller/ContextMenu.jsx new file mode 100644 index 000000000..27d46485d --- /dev/null +++ b/apps/common/mobile/lib/controller/ContextMenu.jsx @@ -0,0 +1,157 @@ +import React, { Component } from 'react'; +import { f7 } from 'framework7-react'; +import { Device } from '../../../../common/mobile/utils/device' + +import ContextMenuView from '../view/ContextMenu'; + +class ContextMenuController extends Component { + constructor(props) { + super(props); + + this.state = { + opened: false + }; + + this.onMenuClosed = this.onMenuClosed.bind(this); + this.onDocumentReady = this.onDocumentReady.bind(this); + this.onApiOpenContextMenu = this.onApiOpenContextMenu.bind(this); + this.onApiHideContextMenu = this.onApiHideContextMenu.bind(this); + } + + onDocumentReady() { + this.$targetEl = $$('#idx-context-menu-target'); + if ( !this.$targetEl.length ) { + // this.$targetEl = $$('
'); + this.$targetEl = $$('
'); + this.$targetEl.css({left: '-10000px', top: '-10000px'}); + + $$('#editor_sdk').append(this.$targetEl); + } + + const api = Common.EditorApi.get(); + api.asc_registerCallback('asc_onShowPopMenu', this.onApiOpenContextMenu); + api.asc_registerCallback('asc_onHidePopMenu', this.onApiHideContextMenu); + } + + offsetPopoverTop(popover) { + var app = popover.app, + $el = popover.$el, + $targetEl = popover.$targetEl; + + const width = $el.width(), + height = $el.height(); + + $el.removeClass('popover-on-left popover-on-right popover-on-top popover-on-bottom popover-on-middle').css({ + left: '', + top: '' + }); + + let targetOffsetLeft, targetOffsetTop; + // var safeAreaTop = parseInt($('html').css('--f7-safe-area-top'), 10); + let safeAreaLeft = parseInt($('html').css('--f7-safe-area-left'), 10), + safeAreaRight = parseInt($('html').css('--f7-safe-area-right'), 10); + // if (Number.isNaN(safeAreaTop)) safeAreaTop = 0; + if (Number.isNaN(safeAreaLeft)) safeAreaLeft = 0; + if (Number.isNaN(safeAreaRight)) safeAreaRight = 0; + + if ($targetEl && $targetEl.length > 0) { + let targetOffset = $targetEl.offset(); + targetOffsetLeft = targetOffset.left - app.left; + targetOffsetTop = targetOffset.top - app.top; + let targetParentPage = $targetEl.parents('.page'); + + if (targetParentPage.length > 0) { + targetOffsetTop -= targetParentPage[0].scrollTop; + } + } + + let position = 'top'; + + let top = targetOffsetTop - height - 10; + top = Math.max(8, Math.min(top, app.height - height - 8)); // Horizontal Position + + let hPosition; + + // if (targetOffsetLeft < app.width / 2) { + // hPosition = 'right'; + // left = position === 'middle' ? targetOffsetLeft + targetWidth : targetOffsetLeft; + // } else { + // hPosition = 'left'; + // left = position === 'middle' ? targetOffsetLeft - width : targetOffsetLeft + targetWidth - width; + // } + + hPosition = 'middle'; + let left = targetOffsetLeft - width / 2; + + left = Math.max(8, Math.min(left, app.width - width - 8 - safeAreaRight), safeAreaLeft); + $el.addClass(`popover-on-${position} popover-on-${hPosition}`); + + $el.css({top: `${top}px`, + left: `${left}px`}); + } + + onApiOpenContextMenu(x, y) { + if ( !this.state.opened ) { + this.$targetEl.css({left: `${x}px`, top: `${y}px`}); + const popover = f7.popover.open('#idx-context-menu-popover', '#idx-context-menu-target'); + + if ( Device.android ) + this.offsetPopoverTop(popover); + + this.setState(state => { + return {opened: true} + }); + } + } + + onApiHideContextMenu() { + if ( this.state.opened ) { + f7.popover.close('#idx-context-menu-popover'); + + this.$targetEl.css({left: '-10000px', top: '-10000px'}); + this.setState({opened: false}); + } + } + + onMenuClosed() { + // (async () => { + // await 1 && this.setState(state => { + // this.$targetEl.css({left: '-10000px', top: '-10000px'}); + // return ({opened: false}); + // }); + // })(); + } + + onMenuItemClick(action) { + } + + 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); + } + + componentDidMount() { + if ( !Common.EditorApi ) { + Common.Notifications.on({ + 'document:ready': this.onDocumentReady + }); + } else { + this.onDocumentReady(); + } + } + + initMenuItems() { + return []; + } + + render() { + return ( + + ) + } +} + +export default ContextMenuController; \ No newline at end of file diff --git a/apps/common/mobile/lib/view/ContextMenu.jsx b/apps/common/mobile/lib/view/ContextMenu.jsx new file mode 100644 index 000000000..c2c8411e2 --- /dev/null +++ b/apps/common/mobile/lib/view/ContextMenu.jsx @@ -0,0 +1,39 @@ +import React, { Component } from 'react'; +import { Popover, List, ListItem, ListButton, Link, Icon } from 'framework7-react'; +import { f7 } from 'framework7-react'; + +class ContextMenuView extends Component { + constructor(props) { + super(props); + } + + componentDidMount() { + // f7.popover.open('#idx-context-menu-popover', '#idx-context-menu-target'); + } + + render() { + const buttons = this.props.items || {}; + + return ( + this.props.onMenuClosed()} + > + + {buttons.map((b, index) => + !!b.text ? + this.props.onMenuItemClick(b.action)} /> : + + + + )} + + + ) + } +} + +export default ContextMenuView; \ No newline at end of file diff --git a/apps/common/mobile/resources/less/contextmenu.less b/apps/common/mobile/resources/less/contextmenu.less new file mode 100644 index 000000000..c41be2a1f --- /dev/null +++ b/apps/common/mobile/resources/less/contextmenu.less @@ -0,0 +1,7 @@ +@import './ios/contextmenu'; +@import './material/contextmenu'; + + +.document-menu { + width: auto; +} \ No newline at end of file diff --git a/apps/common/mobile/resources/less/ios/contextmenu.less b/apps/common/mobile/resources/less/ios/contextmenu.less new file mode 100644 index 000000000..24fc89164 --- /dev/null +++ b/apps/common/mobile/resources/less/ios/contextmenu.less @@ -0,0 +1,102 @@ + +.device-ios { + .document-menu { + @contextMenuBg: rgba(0, 0, 0, 0.9); + @modalHairlineColor: rgba(230, 230, 230, 0.9); + @modalButtonColor: rgba(200, 200, 200, 0.9); + + background-color: @contextMenuBg; + border-radius: 8px; + + .popover-angle { + &:after { + background: @contextMenuBg; + } + } + + .list-block { + font-size: 14px; + white-space: pre; + + &:first-child { + ul { + .hairline-remove(left); + //border-radius: 7px 0 0 7px; + } + + li:first-child a { + //border-radius: 7px 0 0 7px; + border-radius: 0; + } + } + + //&:last-child { + // ul { + // .hairline-remove(right); + // border-radius: 0 7px 7px 0; + // } + // li:last-child a{ + // border-radius: 0 7px 7px 0; + // } + //} + + //&:first-child:last-child { + // li:first-child:last-child a, ul:first-child:last-child { + // border-radius: 7px; + // } + //} + + .item-link { + display: inline-block; + + html:not(.watch-active-state) &:active, &.active-state { + //.transition(0ms); + background-color: #d9d9d9; + .item-inner { + .hairline-color(right, transparent); + } + } + + html.phone & { + padding: 0 10px; + } + } + + .list-button { + color: @white; + line-height: 36px; + + .hairline(right, @modalHairlineColor); + } + + // List items + li { + display: inline-flex; + } + + // Last-childs + li { + &:last-child { + .list-button { + .hairline-remove(right); + } + } + + &:last-child, &:last-child li:last-child { + .item-inner { + .hairline-remove(right); + } + } + + li:last-child, &:last-child li { + .item-inner { + .hairline(right, @modalHairlineColor); + } + } + } + + //.no-hairlines(); + //.no-hairlines-between() + } + } +} \ No newline at end of file diff --git a/apps/common/mobile/resources/less/material/contextmenu.less b/apps/common/mobile/resources/less/material/contextmenu.less new file mode 100644 index 000000000..95476f42a --- /dev/null +++ b/apps/common/mobile/resources/less/material/contextmenu.less @@ -0,0 +1,32 @@ + +.md { + .document-menu { + //line-height: 1 !important; + + .popover-inner { + overflow: hidden; + } + + .list-block { + white-space: pre; + + ul { + height: 48px; + } + + li { + display: inline-block; + } + + .list-button { + color: #212121; + } + + //.item-link { + // html.phone & { + //padding: 0 10px; + //} + //} + } + } +} diff --git a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx index 580c35a7a..99bc67e89 100644 --- a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx +++ b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx @@ -1,74 +1,38 @@ -import React, { Component } from 'react'; +import React from 'react'; import { f7 } from 'framework7-react'; +import ContextMenuController from '../../../../common/mobile/lib/controller/ContextMenu'; -import ContextMenuView from '../view/ContextMenu'; - -class ContextMenuController extends Component { +class ContextMenu extends ContextMenuController { constructor(props) { super(props); - this.state = { - pos: [-10000,-10000], - show: false - }; + // console.log('context menu controller created'); + } - this.onMenuClosed = this.onMenuClosed.bind(this); + // onMenuClosed() { + // super.onMenuClosed(); + // } - Common.Notifications.on({ - 'engineCreated': api => { - api.asc_registerCallback('asc_onShowPopMenu', this.onApiShowPopMenu.bind(this)); - api.asc_registerCallback('asc_onHidePopMenu', this.onApiHidePopMenu.bind(this)); - }, - 'document:ready': () => { - this.$targetEl = $$('
'); - this.$targetEl.css({left: '-10000px', top: '-10000px'}); + onMenuItemClick(action) { + super.onMenuItemClick(action); - $$('#editor_sdk').append(this.$targetEl); + console.log("on click item"); + } + + initMenuItems() { + return [ + { + text: 'Edit', + action: 'edit' + }, { + text: 'View', + action: 'view' + }, { + icon: 'icon-paste', + action: 'review' } - }); - - console.log('context menu controller created'); - } - - onApiShowPopMenu(x, y) { - console.log('show context menu ' + [x,y]); - - this.$targetEl.css({left: `${x}px`, top: `${y}px`}); - this.setState({ - pos: [x,y], - show: true }); - } - - onApiHidePopMenu() { - // console.log('hide context menu'); - - if ( this.state.show ) { - f7.popover.close('#idx-context-menu-popover'); - } - } - - onMenuClosed() { - (async () => { - await 1 && this.setState(state => { - this.$targetEl.css({left: '-10000px', top: '-10000px'}); - return ({pos: [-10000, -10000], show: false}); - }); - })(); - } - - componentWillUnmount() { - console.log('context menu controller will be unmounted'); - } - - componentDidMount() { - console.log('context menu controller did mount'); - } - - render() { - return ( - !this.state.show ? null : - ) + ]; } } -export { ContextMenuController as ContextMenu }; \ No newline at end of file +export { ContextMenu }; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/less/app.less b/apps/documenteditor/mobile/src/less/app.less index 3caaf81f1..cb7039a9c 100644 --- a/apps/documenteditor/mobile/src/less/app.less +++ b/apps/documenteditor/mobile/src/less/app.less @@ -7,8 +7,10 @@ @import '../../../../common/mobile/resources/less/common.less'; @import '../../../../common/mobile/resources/less/common-ios.less'; @import '../../../../common/mobile/resources/less/common-material.less'; +@import '../../../../common/mobile/resources/less/icons.less'; @import '../../../../common/mobile/resources/less/dataview.less'; @import '../../../../common/mobile/resources/less/search.less'; +@import '../../../../common/mobile/resources/less/contextmenu.less'; @import './app-material.less'; @import './app-ios.less'; @import './icons-ios.less'; diff --git a/apps/documenteditor/mobile/src/page/main.jsx b/apps/documenteditor/mobile/src/page/main.jsx index fbfc689c6..96a2c81c7 100644 --- a/apps/documenteditor/mobile/src/page/main.jsx +++ b/apps/documenteditor/mobile/src/page/main.jsx @@ -90,7 +90,7 @@ export default class MainPage extends Component { !this.state.collaborationVisible ? null : } - {/**/} + ) } diff --git a/apps/documenteditor/mobile/src/view/ContextMenu.jsx b/apps/documenteditor/mobile/src/view/ContextMenu.jsx index 6f05e4965..c2c8411e2 100644 --- a/apps/documenteditor/mobile/src/view/ContextMenu.jsx +++ b/apps/documenteditor/mobile/src/view/ContextMenu.jsx @@ -2,29 +2,18 @@ import React, { Component } from 'react'; import { Popover, List, ListItem, ListButton, Link, Icon } from 'framework7-react'; import { f7 } from 'framework7-react'; -const buttons = [ - { - text: 'Edit', - action: 'edit' - }, { - text: 'View', - action: 'view' - }, { - icon: 'icon-paste', - action: 'review' - } -]; - class ContextMenuView extends Component { constructor(props) { super(props); } componentDidMount() { - f7.popover.open('#idx-context-menu-popover', '#idx-context-menu-target'); + // f7.popover.open('#idx-context-menu-popover', '#idx-context-menu-target'); } render() { + const buttons = this.props.items || {}; + return ( {buttons.map((b, index) => !!b.text ? - : + this.props.onMenuItemClick(b.action)} /> : - + )}