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..125112339 100644 --- a/apps/common/mobile/lib/controller/collaboration/Comments.jsx +++ b/apps/common/mobile/lib/controller/collaboration/Comments.jsx @@ -60,12 +60,12 @@ class CommentsController extends Component { api.asc_registerCallback('asc_onChangeCommentData', this.changeCommentData.bind(this)); api.asc_registerCallback('asc_onShowComment', this.changeShowComments.bind(this)); api.asc_registerCallback('asc_onHideComment', this.hideComments.bind(this)); - }); - Common.Notifications.on('comments:filterchange', this.onFilterChange.bind(this)); // for sse - - Common.Notifications.on('configOptionsFill', () => { - this.curUserId = this.appOptions.user.id; + if (window.editorType === 'sse') { + api.asc_registerCallback('asc_onActiveSheetChanged', this.onApiActiveSheetChanged.bind(this)); + Common.Notifications.on('comments:filterchange', this.onFilterChange.bind(this)); + Common.Notifications.on('sheet:active', this.onApiActiveSheetChanged.bind(this)); + } }); Common.Notifications.on('document:ready', () => { @@ -77,8 +77,13 @@ class CommentsController extends Component { isLiveCommenting ? api.asc_showComments(resolved) : api.asc_hideComments(); /** coauthoring end **/ } + + this.curUserId = this.props.users.currentUser.asc_getIdOriginal(); }); } + onApiActiveSheetChanged (index) { + this.onFilterChange(['doc', 'sheet' + Common.EditorApi.get().asc_getWorksheetId(index)]); + } addComment (id, data) { const comment = this.readSDKComment(id, data); if (comment) { @@ -238,6 +243,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, @@ -264,10 +272,7 @@ class AddCommentController extends Component { !!comment.asc_putDocumentFlag && comment.asc_putDocumentFlag(documentFlag); api.asc_addComment(comment); - - return true; } - return false; } render() { return( @@ -308,10 +313,10 @@ class EditCommentController extends Component { ascComment.asc_putDocumentFlag(comment.unattached); } - var reply = comment.replies; + const reply = comment.replies; if (reply && reply.length > 0) { reply.forEach((reply) => { - var addReply = (typeof Asc.asc_CCommentDataWord !== 'undefined' ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null)); + const addReply = (typeof Asc.asc_CCommentDataWord !== 'undefined' ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null)); if (addReply) { addReply.asc_putText(reply.reply); addReply.asc_putTime(utcDateToString(new Date(reply.time))); diff --git a/apps/common/mobile/lib/store/comments.js b/apps/common/mobile/lib/store/comments.js index 7852e0d0f..86248d9d2 100644 --- a/apps/common/mobile/lib/store/comments.js +++ b/apps/common/mobile/lib/store/comments.js @@ -7,7 +7,6 @@ export class storeComments { collectionComments: observable, groupCollectionComments: observable, filter: observable, - groupCollectionFilter: observable, showComments: observable, changeShowComment: action, @@ -17,7 +16,7 @@ export class storeComments { changeComment: action, changeFilter: action, - sortComments: computed, + groupCollectionFilter: computed, isOpenEditComment: observable, openEditComment: action, @@ -31,7 +30,6 @@ export class storeComments { groupCollectionComments = []; filter = undefined; - groupCollectionFilter = []; // for sse showComments = []; changeShowComment (uid) { @@ -42,54 +40,16 @@ export class storeComments { } addComment (comment) { - comment.groupName ? this.addCommentToGroupCollection(comment) : this.addCommentToCollection(comment); - } - - addCommentToCollection (comment) { - this.collectionComments.push(comment); - } - - addCommentToGroupCollection (comment) { - const groupName = comment.groupName; - if (!this.groupCollectionComments[groupName]) { - this.groupCollectionComments[groupName] = []; - } - this.groupCollectionComments[groupname].push(comment); - if (this.filter.indexOf(groupname) !== -1) { - this.groupCollectionFilter.push(comment); - } + comment.groupName ? this.groupCollectionComments.push(comment) : this.collectionComments.push(comment); } removeComment (id) { - if (this.collectionComments.length > 0) { - this.removeCommentFromCollection(id); - } else { - this.removeCommentFromGroups(id); - } - } - - removeCommentFromCollection (id) { - const index = this.collectionComments.findIndex((comment) => { + const collection = this.collectionComments.length > 0 ? this.collectionComments : this.groupCollectionComments; + const index = collection.findIndex((comment) => { return comment.uid === id; }); if (index !== -1) { - this.collectionComments.splice(index, 1); - } - } - - removeCommentFromGroups (id) { - for (let name in this.groupCollectionComments) { - const store = this.groupCollectionComments[name]; - const comment = store.find((item) => { - return item.uid === id; - }); - const index = store.indexOf(comment); - if (index !== -1) { - this.groupCollectionComments[name].splice(index, 1); - if (this.filter.indexOf(name) !== -1) { - this.groupCollectionFilter.splice(this.groupCollectionFilter.indexOf(comment), 1); - } - } + collection.splice(index, 1); } } @@ -111,43 +71,28 @@ export class storeComments { } changeFilter (filter) { - let comments = []; this.filter = filter; - filter.forEach((item) => { - if (!this.groupCollectionComments[item]) - this.groupCollectionComments[item] = []; - comments = comments.concat(this.groupCollectionComments[item]); - }); - this.groupCollectionFilter = comments; } findComment (id) { - let comment = this.collectionComments.find((item) => { + const collection = this.collectionComments.length > 0 ? this.collectionComments : this.groupCollectionComments; + let comment = collection.find((item) => { return item.uid === id; }); - if (!comment) { - comment = this.findCommentInGroup(id); - } return comment; } - findCommentInGroup (id) { - let model; - for (let name in this.groupCollectionComments) { - const store = this.groupCollectionComments[name]; - const id = id.isArray() ? id[0] : id; - model = store.find((item) => { - return item.uid === id; + get groupCollectionFilter () { + if (this.filter && this.groupCollectionComments.length > 0) { + const arr = []; + this.filter.forEach((groupName) => { + this.groupCollectionComments.forEach((comment) => { + if (comment.groupName === groupName) { + arr.push(comment); + } + }); }); - if (model) return model; - } - return model; - } - - get sortComments () { - const comments = (this.groupCollectionFilter.length !== 0) ? this.groupCollectionFilter : (this.collectionComments.length !== 0 ? this.collectionComments : false); - if (comments.length > 0) { - return [...comments].sort((a, b) => a.time > b.time ? 1 : -1); + return arr; } return false; } 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/spreadsheeteditor/mobile/src/view/settings/SpreadsheetAbout.jsx b/apps/common/mobile/lib/view/About.jsx similarity index 57% rename from apps/spreadsheeteditor/mobile/src/view/settings/SpreadsheetAbout.jsx rename to apps/common/mobile/lib/view/About.jsx index 532e0435a..a78dd577d 100644 --- a/apps/spreadsheeteditor/mobile/src/view/settings/SpreadsheetAbout.jsx +++ b/apps/common/mobile/lib/view/About.jsx @@ -3,12 +3,12 @@ import { observer, inject } from "mobx-react"; import { Page, Navbar, Link } from "framework7-react"; import { useTranslation } from "react-i18next"; -const PageSpreadsheetAbout = props => { +const PageAbout = props => { const { t } = useTranslation(); - const _t = t("View.Settings", { returnObjects: true }); - const storeAppOptions = props.storeAppOptions; - const isCanBranding = storeAppOptions.canBranding; - const licInfo = isCanBranding ? storeAppOptions.customization : null; + const _t = t("About", { returnObjects: true }); + const store = props.storeAppOptions; + const isCanBranding = store.canBranding; + const licInfo = isCanBranding ? store.customization : null; const customer = licInfo ? licInfo.customer : null; const nameCustomer = customer ? customer.name : null; const mailCustomer = customer ? customer.mail : null; @@ -17,27 +17,32 @@ const PageSpreadsheetAbout = props => { const infoCustomer = customer ? customer.info : null; const logoCustomer = customer ? customer.logo : null; - // console.log(storeAppOptions); - // console.log(isCanBranding); + const publisherUrl = __PUBLISHER_URL__, + publisherPrintUrl = publisherUrl.replace(/https?:\/{2}|\/$/,""); + + const editors = { + de: 'DOCUMENT EDITOR', + pe: 'PRESENTATION EDITOR', + sse: 'SPREADSHEET EDITOR' + }; + + const nameEditor = editors[editorType]; return ( - {licInfo && typeof licInfo == 'object' && typeof(customer) == 'object' ? + {licInfo && typeof licInfo == 'object' && typeof(customer) == 'object' ? (
- {/* {licInfo && typeof licInfo == 'object' && typeof(customer) == 'object' ? null : ( - - )} */} {logoCustomer && logoCustomer.length ? ( ) : null}
-

SPREADSHEET EDITOR

-

{_t.textVersion} 6.1.1

+

{nameEditor}

+

{_t.textVersion} {__PRODUCT_VERSION__}

{nameCustomer && nameCustomer.length ? ( @@ -45,20 +50,16 @@ const PageSpreadsheetAbout = props => { ) : null} {addressCustomer && addressCustomer.length ? (

- + {addressCustomer}

) : null} {mailCustomer && mailCustomer.length ? (

- + {mailCustomer}

) : null} -

- - +371 633-99867 -

{urlCustomer && urlCustomer.length ? (

{

-

Ascensio System SIA

+

{__PUBLISHER_NAME__}

- www.onlyoffice.com + {publisherPrintUrl}

-
: + + ) : (
- +
-
} +
+

{nameEditor}

+

{_t.textVersion} {__PRODUCT_VERSION__}

+
+
+

{__PUBLISHER_NAME__}

+

+ + {__PUBLISHER_ADDRESS__} +

+

+ + {__SUPPORT_EMAIL__} +

+

+ + {__PUBLISHER_PHONE__} +

+

+ {publisherPrintUrl} +

+
+ + )}
); }; -const SpreadsheetAbout = inject("storeAppOptions")(observer(PageSpreadsheetAbout)); +const About = inject("storeAppOptions")(observer(PageAbout)); -export default SpreadsheetAbout; \ No newline at end of file +export default About; \ No newline at end of file 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/lib/view/collaboration/Comments.jsx b/apps/common/mobile/lib/view/collaboration/Comments.jsx index e76f84edc..2fd0dbdfa 100644 --- a/apps/common/mobile/lib/view/collaboration/Comments.jsx +++ b/apps/common/mobile/lib/view/collaboration/Comments.jsx @@ -31,18 +31,21 @@ const AddCommentPopup = inject("storeComments")(observer(props => { { - props.closeAddComment(); f7.popup.close('.add-comment-popup'); + setTimeout(() => { + props.closeAddComment(); + }, 500) }}>{_t.textCancel} {_t.textAddComment} { - if (props.onAddNewComment(stateText, false)) { + f7.popup.close('.add-comment-popup'); + setTimeout(() => { props.closeAddComment(); - f7.popup.close('.add-comment-popup'); - } + props.onAddNewComment(stateText, false) + }, 500); }}> {Device.android ? : _t.textDone} @@ -104,9 +107,10 @@ const AddCommentDialog = inject("storeComments")(observer(props => { const done = document.getElementById('comment-done'); done.addEventListener('click', () => { const value = document.getElementById('comment-text').value; - if (value.length > 0 && props.onAddNewComment(value, false)) { + if (value.length > 0) { f7.dialog.close(); props.closeAddComment(); + props.onAddNewComment(value, false); } }); const area = document.getElementById('comment-text'); @@ -193,16 +197,20 @@ const EditCommentPopup = inject("storeComments")(observer(({storeComments, comme { f7.popup.close('.edit-comment-popup'); - storeComments.openEditComment(false); + setTimeout(() => { + storeComments.openEditComment(false); + }, 500); }}>{_t.textCancel} {_t.textEditComment} { - onEditComment(comment, stateText); f7.popup.close('.edit-comment-popup'); - storeComments.openEditComment(false); + setTimeout(() => { + storeComments.openEditComment(false); + onEditComment(comment, stateText); + }, 500); }} > {Device.android ? : _t.textDone} @@ -313,17 +321,21 @@ const AddReplyPopup = inject("storeComments")(observer(({storeComments, userInfo { - storeComments.openAddReply(false); f7.popup.close('.add-reply-popup'); + setTimeout(() => { + storeComments.openAddReply(false); + }, 500); }}>{_t.textCancel} {_t.textAddReply} { - onAddReply(comment, stateText); - storeComments.openAddReply(false); f7.popup.close('.add-reply-popup'); + setTimeout(() => { + storeComments.openAddReply(false); + onAddReply(comment, stateText); + }, 500); }}> {Device.android ? : _t.textDone} @@ -429,16 +441,20 @@ const EditReplyPopup = inject("storeComments")(observer(({storeComments, comment { f7.popup.close('.edit-reply-popup'); - storeComments.openEditReply(false); + setTimeout(() => { + storeComments.openEditReply(false); + }, 500); }}>{_t.textCancel} {_t.textEditReply} { - onEditReply(comment, reply, stateText); f7.popup.close('.edit-reply-popup'); - storeComments.openEditReply(false); + setTimeout(() => { + storeComments.openEditReply(false); + onEditReply(comment, reply, stateText); + }, 500); }} > {Device.android ? : _t.textDone} @@ -544,7 +560,8 @@ const ViewComments = ({storeComments, storeAppOptions, onCommentMenuClick, onRes const isAndroid = Device.android; const viewMode = !storeAppOptions.canComments; - const comments = storeComments.sortComments; + const comments = storeComments.groupCollectionFilter || storeComments.collectionComments; + const sortComments = comments.length > 0 ? [...comments].sort((a, b) => a.time > b.time ? 1 : -1) : null; const [clickComment, setComment] = useState(); const [commentActionsOpened, openActionComment] = useState(false); @@ -555,10 +572,10 @@ const ViewComments = ({storeComments, storeAppOptions, onCommentMenuClick, onRes return ( - {!comments ? + {!sortComments ?
{_t.textNoComments}
: - {comments.map((comment, indexComment) => { + {sortComments.map((comment, indexComment) => { return (
diff --git a/apps/common/mobile/resources/less/common-ios.less b/apps/common/mobile/resources/less/common-ios.less index bf9814c77..83ffd0b6f 100644 --- a/apps/common/mobile/resources/less/common-ios.less +++ b/apps/common/mobile/resources/less/common-ios.less @@ -391,6 +391,10 @@ } } + .content-block { + color: @blockTitleColor; + } + .dataview, #add-table, #add-shape, #add-slide, #add-chart { &.page-content, .page-content { background-color: @white; @@ -490,4 +494,17 @@ } } } + + .actions-button { + background: rgba(255,255,255,.95); + } + + .actions-button-text { + height: 57px; + line-height: 57px; + font-size: 20px; + color: @themeColor; + white-space: normal; + text-overflow: ellipsis; + } } diff --git a/apps/common/mobile/resources/less/common-material.less b/apps/common/mobile/resources/less/common-material.less index b141870d4..a1a938939 100644 --- a/apps/common/mobile/resources/less/common-material.less +++ b/apps/common/mobile/resources/less/common-material.less @@ -431,4 +431,19 @@ } } } + + .actions-button-text { + cursor: pointer; + line-height: 48px; + font-size: 16px; + color: rgba(0,0,0,.87); + } + + @media (min-width: 496px) { + .actions-modal { + width: 100%; + left: auto; + margin-left: 0; + } + } } diff --git a/apps/common/mobile/resources/less/common.less b/apps/common/mobile/resources/less/common.less index 8d2746af8..ad82957bc 100644 --- a/apps/common/mobile/resources/less/common.less +++ b/apps/common/mobile/resources/less/common.less @@ -84,6 +84,15 @@ } } +.about { + .content-block { + margin: 0 auto 15px; + a { + color: @black; + } + } +} + .content-block { margin: 32px 0; padding: 0 16px; @@ -669,6 +678,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/icons.less b/apps/common/mobile/resources/less/icons.less index df3a49f3f..fdea10609 100644 --- a/apps/common/mobile/resources/less/icons.less +++ b/apps/common/mobile/resources/less/icons.less @@ -7,6 +7,16 @@ i.icon { height: 24px; .encoded-svg-uncolored-mask(''); } + &.icon-copy { + width: 24px; + height: 24px; + .encoded-svg-uncolored-mask(''); + } + &.icon-cut { + width: 24px; + height: 24px; + .encoded-svg-uncolored-mask(''); + } &.icon-prev:after, &.icon-next:after { content: none; } 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/locale/en.json b/apps/documenteditor/mobile/locale/en.json index e8858a48a..d85eb309c 100644 --- a/apps/documenteditor/mobile/locale/en.json +++ b/apps/documenteditor/mobile/locale/en.json @@ -145,7 +145,9 @@ "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" + "textDoNotShowAgain": "Don't show again", + "textColumns": "Columns", + "textRows": "Rows" }, "Settings": { "textCancel": "Cancel", @@ -384,5 +386,14 @@ "textStartAt": "Start At", "textLocation": "Location", "textFormat": "Format" + }, + "About": { + "textAbout": "About", + "textVersion": "Version", + "textEmail": "Email", + "textAddress": "Address", + "textTel": "Tel", + "textPoweredBy": "Powered By", + "textBack": "Back" } } \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx index 9911a4920..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(); @@ -89,6 +97,38 @@ class ContextMenu extends ContextMenuController { this.props.openOptions('coauth', 'cm-review-change'); }, 400); break; + case 'merge': + api.MergeCells(); + break; + case 'split': + this.showSplitModal(); + break; + case 'delete': + api.asc_Remove(); + break; + case 'deletetable': + api.remTable(); + 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); @@ -117,6 +157,66 @@ class ContextMenu extends ContextMenuController { }).open(); } + showSplitModal() { + const { t } = this.props; + const _t = t("ContextMenu", { returnObjects: true }); + let picker; + const dialog = f7.dialog.create({ + title: _t.menuSplit, + text: '', + content: + '
' + + '
' + + '
' + _t.textColumns + '
' + + '
' + _t.textRows + '
' + + '
' + + '
' + + '
', + buttons: [ + { + text: _t.menuCancel + }, + { + text: 'OK', + bold: true, + onClick: function () { + const size = picker.value; + Common.EditorApi.get().SplitCell(parseInt(size[0]), parseInt(size[1])); + } + } + ] + }).open(); + dialog.on('opened', () => { + picker = f7.picker.create({ + containerEl: document.getElementById('picker-split-size'), + cols: [ + { + textAlign: 'center', + width: '100%', + values: [1,2,3,4,5,6,7,8,9,10] + }, + { + textAlign: 'center', + width: '100%', + values: [1,2,3,4,5,6,7,8,9,10] + } + ], + toolbar: false, + rotateEffect: true, + value: [3, 3] + }); + }); + } + + openLink(url) { + if (Common.EditorApi.get().asc_getUrlType(url) > 0) { + const newDocumentPage = window.open(url, '_blank'); + if (newDocumentPage) { + newDocumentPage.focus(); + } + } + } + onDocumentReady() { super.onDocumentReady(); @@ -132,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(); @@ -202,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', @@ -255,12 +355,12 @@ class ContextMenu extends ContextMenuController { }); } - // if ( !_.isEmpty(api.can_AddHyperlink()) && !lockedHeader) { - // arrItems.push({ - // caption: _t.menuAddLink, - // event: 'addlink' - // }); - // } + if ( !!api.can_AddHyperlink() && !lockedHeader) { + itemsText.push({ + caption: _t.menuAddLink, + event: 'addlink' + }); + } if ( canReview ) { if (this.inRevisionChange) { diff --git a/apps/documenteditor/mobile/src/controller/Main.jsx b/apps/documenteditor/mobile/src/controller/Main.jsx index 05824fd1d..74705ba10 100644 --- a/apps/documenteditor/mobile/src/controller/Main.jsx +++ b/apps/documenteditor/mobile/src/controller/Main.jsx @@ -13,6 +13,8 @@ import { ViewCommentsController } from "../../../../common/mobile/lib/controller/collaboration/Comments"; +import patch from '../lib/patch' + @inject( "storeAppOptions", "storeDocumentSettings", @@ -56,6 +58,7 @@ class MainController extends Component { }; const loadConfig = data => { + patch.isSupportEditFeature(); console.log('load config'); this.editorConfig = Object.assign({}, this.editorConfig, data.config); diff --git a/apps/documenteditor/mobile/src/controller/add/AddLink.jsx b/apps/documenteditor/mobile/src/controller/add/AddLink.jsx new file mode 100644 index 000000000..3a6ea52a5 --- /dev/null +++ b/apps/documenteditor/mobile/src/controller/add/AddLink.jsx @@ -0,0 +1,67 @@ +import React, {Component} from 'react'; +import { f7 } from 'framework7-react'; +import {Device} from '../../../../../common/mobile/utils/device'; +import { withTranslation} from 'react-i18next'; + +import {PageAddLink} from '../../view/add/AddLink'; + +class AddLinkController extends Component { + constructor (props) { + super(props); + this.onInsertLink = this.onInsertLink.bind(this); + } + + closeModal () { + if ( Device.phone ) { + f7.sheet.close('.add-popup', true); + } else { + f7.popover.close('#add-popover'); + } + } + + getDisplayLinkText () { + const api = Common.EditorApi.get(); + return api.can_AddHyperlink(); + } + + onInsertLink (url, display, tip) { + const api = Common.EditorApi.get(); + + const { t } = this.props; + const _t = t("Add", { returnObjects: true }); + + const urltype = api.asc_getUrlType(url.trim()); + const isEmail = (urltype == 2); + + if (urltype < 1) { + f7.dialog.alert(_t.txtNotUrl, _t.notcriticalErrorTitle); + return; + } + + let _url = url.replace(/^\s+|\s+$/g,''); + + if (! /(((^https?)|(^ftp)):\/\/)|(^mailto:)/i.test(_url) ) + _url = (isEmail ? 'mailto:' : 'http://' ) + _url; + + _url = _url.replace(new RegExp("%20",'g')," "); + + const props = new Asc.CHyperlinkProperty(); + props.put_Value(_url); + props.put_Text(!display ? _url : display); + props.put_ToolTip(tip); + + api.add_Hyperlink(props); + + this.closeModal(); + } + + render () { + return ( + + ) + } +} + +const AddLinkWithTranslation = withTranslation()(AddLinkController); + +export {AddLinkWithTranslation as AddLinkController}; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/controller/add/AddOther.jsx b/apps/documenteditor/mobile/src/controller/add/AddOther.jsx index 12017600f..3d5ff6b15 100644 --- a/apps/documenteditor/mobile/src/controller/add/AddOther.jsx +++ b/apps/documenteditor/mobile/src/controller/add/AddOther.jsx @@ -66,7 +66,6 @@ const _Rometo10 = (str) => { class AddOtherController extends Component { constructor (props) { super(props); - this.onInsertLink = this.onInsertLink.bind(this); this.onInsertPageNumber = this.onInsertPageNumber.bind(this); this.onPageBreak = this.onPageBreak.bind(this); this.onColumnBreak = this.onColumnBreak.bind(this); @@ -86,42 +85,6 @@ class AddOtherController extends Component { } } - getDisplayLinkText () { - const api = Common.EditorApi.get(); - return api.can_AddHyperlink(); - } - - onInsertLink (url, display, tip) { - const api = Common.EditorApi.get(); - - const { t } = this.props; - const _t = t("Add", { returnObjects: true }); - - const urltype = api.asc_getUrlType(url.trim()); - const isEmail = (urltype == 2); - - if (urltype < 1) { - f7.dialog.alert(_t.txtNotUrl, _t.notcriticalErrorTitle); - return; - } - - let _url = url.replace(/^\s+|\s+$/g,''); - - if (! /(((^https?)|(^ftp)):\/\/)|(^mailto:)/i.test(_url) ) - _url = (isEmail ? 'mailto:' : 'http://' ) + _url; - - _url = _url.replace(new RegExp("%20",'g')," "); - - const props = new Asc.CHyperlinkProperty(); - props.put_Value(_url); - props.put_Text(!display ? _url : display); - props.put_ToolTip(tip); - - api.add_Hyperlink(props); - - this.closeModal(); - } - onInsertPageNumber (type) { const api = Common.EditorApi.get(); @@ -280,8 +243,6 @@ class AddOtherController extends Component { render () { return ( { + return null +}; + +patch.isSupportEditFeature = () => { + return false +}; + +export default patch; diff --git a/apps/documenteditor/mobile/src/page/main.jsx b/apps/documenteditor/mobile/src/page/main.jsx index 092547c96..c3acac141 100644 --- a/apps/documenteditor/mobile/src/page/main.jsx +++ b/apps/documenteditor/mobile/src/page/main.jsx @@ -18,25 +18,29 @@ export default class MainPage extends Component { this.state = { editOptionsVisible: false, addOptionsVisible: false, + addShowOptions: null, settingsVisible: false, collaborationVisible: false }; } - handleClickToOpenOptions = (opts, page) => { + 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' ) return { collaborationVisible: true, - collaborationPage: page + collaborationPage: showOpts }; }); }; @@ -90,7 +94,7 @@ export default class MainPage extends Component { } { !this.state.addOptionsVisible ? null : - + } { !this.state.settingsVisible ? null : diff --git a/apps/documenteditor/mobile/src/view/add/Add.jsx b/apps/documenteditor/mobile/src/view/add/Add.jsx index 486c57dd3..c691dfce4 100644 --- a/apps/documenteditor/mobile/src/view/add/Add.jsx +++ b/apps/documenteditor/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 React, {Component, useEffect, Fragment} from '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"; @@ -8,10 +8,11 @@ import {Device} from '../../../../../common/mobile/utils/device'; import {AddTableController} from "../../controller/add/AddTable"; import AddShapeController from "../../controller/add/AddShape"; import {AddImageController} from "../../controller/add/AddImage"; +import {AddLinkController} from "../../controller/add/AddLink"; import {AddOtherController} from "../../controller/add/AddOther"; import {PageImageLinkSettings} from "../add/AddImage"; -import {PageAddLink, PageAddNumber, PageAddBreak, PageAddSectionBreak, PageAddFootnote} from "../add/AddOther"; +import {PageAddNumber, PageAddBreak, PageAddSectionBreak, PageAddFootnote} from "../add/AddOther"; const routes = [ // Image @@ -22,7 +23,7 @@ const routes = [ // Other { path: '/add-link/', - component: PageAddLink, + component: AddLinkController, }, { path: '/add-page-number/', @@ -44,15 +45,20 @@ const routes = [ const AddLayoutNavbar = ({ tabs, inPopover }) => { const isAndroid = Device.android; + const { t } = useTranslation(); + const _t = t('Add', {returnObjects: true}); return ( -
- {tabs.map((item, index) => - - - )} - {isAndroid && } -
+ {tabs.length > 1 ? +
+ {tabs.map((item, index) => + + + )} + {isAndroid && } +
: + { tabs[0].caption } + } { !inPopover && }
) @@ -73,31 +79,41 @@ const AddLayoutContent = ({ tabs }) => { const AddTabs = props => { const { t } = useTranslation(); const _t = t('Add', {returnObjects: true}); + const showPanels = props.showPanels; const tabs = []; - tabs.push({ - caption: _t.textTable, - id: 'add-table', - icon: 'icon-add-table', - 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.textTable, + id: 'add-table', + icon: 'icon-add-table', + 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 ( @@ -122,10 +138,10 @@ class AddView extends Component { return ( show_popover ? this.props.onclosed()}> - + : this.props.onclosed()}> - + ) } @@ -145,7 +161,7 @@ const Add = props => { if ( props.onclosed ) props.onclosed(); }; - return + return }; export default Add; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/view/add/AddLink.jsx b/apps/documenteditor/mobile/src/view/add/AddLink.jsx new file mode 100644 index 000000000..8239d3819 --- /dev/null +++ b/apps/documenteditor/mobile/src/view/add/AddLink.jsx @@ -0,0 +1,51 @@ +import React, {useState} from 'react'; +import {List, Page, Navbar, Icon, ListButton, ListInput} from 'framework7-react'; +import { useTranslation } from 'react-i18next'; +import {Device} from "../../../../../common/mobile/utils/device"; + +const PageLink = props => { + const { t } = useTranslation(); + const _t = t('Add', {returnObjects: true}); + + let display = props.getDisplayLinkText(); + display = typeof display === 'string' ? display : ''; + + const [stateLink, setLink] = useState(''); + const [stateDisplay, setDisplay] = useState(display); + const [stateTip, setTip] = useState(''); + return ( + + {!props.noNavbar && } + + {setLink(event.target.value)}} + > + {setDisplay(event.target.value)}} + > + {setTip(event.target.value)}} + > + + + { + props.onInsertLink(stateLink, stateDisplay, stateTip) + }}> + + + ) +}; + +export {PageLink as PageAddLink}; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/view/add/AddOther.jsx b/apps/documenteditor/mobile/src/view/add/AddOther.jsx index 25040fc49..0ea3485a1 100644 --- a/apps/documenteditor/mobile/src/view/add/AddOther.jsx +++ b/apps/documenteditor/mobile/src/view/add/AddOther.jsx @@ -4,51 +4,6 @@ import {List, ListItem, Page, Navbar, Icon, ListButton, ListInput, BlockTitle, S import { useTranslation } from 'react-i18next'; import {Device} from "../../../../../common/mobile/utils/device"; -const PageLink = props => { - const { t } = useTranslation(); - const _t = t('Add', {returnObjects: true}); - - let display = props.getDisplayLinkText(); - display = typeof display === 'string' ? display : ''; - - const [stateLink, setLink] = useState(''); - const [stateDisplay, setDisplay] = useState(display); - const [stateTip, setTip] = useState(''); - return ( - - - - {setLink(event.target.value)}} - > - {setDisplay(event.target.value)}} - > - {setTip(event.target.value)}} - > - - - { - props.onInsertLink(stateLink, stateDisplay, stateTip) - }}> - - - ) -}; - const PageNumber = props => { const { t } = useTranslation(); const _t = t('Add', {returnObjects: true}); @@ -240,7 +195,6 @@ const AddOther = props => { const AddOtherContainer = inject("storeComments")(observer(AddOther)); export {AddOtherContainer as AddOther, - PageLink as PageAddLink, PageNumber as PageAddNumber, PageBreak as PageAddBreak, PageSectionBreak as PageAddSectionBreak, diff --git a/apps/documenteditor/mobile/src/view/settings/Settings.jsx b/apps/documenteditor/mobile/src/view/settings/Settings.jsx index 63e744df1..71359798e 100644 --- a/apps/documenteditor/mobile/src/view/settings/Settings.jsx +++ b/apps/documenteditor/mobile/src/view/settings/Settings.jsx @@ -11,6 +11,7 @@ import { DownloadController } from "../../controller/settings/Download"; import ApplicationSettingsController from "../../controller/settings/ApplicationSettings"; import { DocumentFormats, DocumentMargins, DocumentColorSchemes } from "./DocumentSettings"; import { MacrosSettings } from "./ApplicationSettings"; +import About from '../../../../../common/mobile/lib/view/About'; const routes = [ { @@ -48,6 +49,10 @@ const routes = [ { path: '/color-schemes/', component: DocumentColorSchemes + }, + { + path: '/about/', + component: About } ]; @@ -176,7 +181,7 @@ const SettingsList = inject("storeAppOptions")( observer( withTranslation()( pro } {_canAbout && - + } diff --git a/apps/presentationeditor/mobile/locale/en.json b/apps/presentationeditor/mobile/locale/en.json index 15ec8dca9..b90038922 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,36 @@ "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" } + }, + "About": { + "textAbout": "About", + "textVersion": "Version", + "textEmail": "Email", + "textAddress": "Address", + "textTel": "Tel", + "textPoweredBy": "Powered By", + "textBack": "Back" } } \ 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..0db776dcc --- /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, 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' + }); + } + + const 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 8d67cd2bf..177f720ed 100644 --- a/apps/presentationeditor/mobile/src/controller/Main.jsx +++ b/apps/presentationeditor/mobile/src/controller/Main.jsx @@ -1,14 +1,21 @@ -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 { constructor(props) { super(props) + window.editorType = 'pe'; } initSdk() { @@ -336,6 +343,8 @@ class MainController extends Component { Common.Gateway.documentReady(); f7.emit('resize'); + + Common.Notifications.trigger('document:ready'); } _onOpenDocumentProgress(progress) { @@ -348,7 +357,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/controller/settings/PresentationAbout.jsx b/apps/presentationeditor/mobile/src/controller/settings/PresentationAbout.jsx deleted file mode 100644 index db46185db..000000000 --- a/apps/presentationeditor/mobile/src/controller/settings/PresentationAbout.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from "react"; -import PresentationAbout from "../../view/settings/PresentationAbout"; - -class PresentationAboutController extends Component { - constructor(props) { - super(props); - } - - render() { - return ( - - ); - } -} - - -export default PresentationAboutController; \ No newline at end of file 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/less/icons-ios.less b/apps/presentationeditor/mobile/src/less/icons-ios.less index d43e2a6b4..2b1302da0 100644 --- a/apps/presentationeditor/mobile/src/less/icons-ios.less +++ b/apps/presentationeditor/mobile/src/less/icons-ios.less @@ -476,24 +476,6 @@ .encoded-svg-background(''); } - &.icon-cut { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - - &.icon-copy { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - - &.icon-paste { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - //comments &.icon-menu-comment { width: 30px; diff --git a/apps/presentationeditor/mobile/src/less/icons-material.less b/apps/presentationeditor/mobile/src/less/icons-material.less index 3c7098570..94411dd12 100644 --- a/apps/presentationeditor/mobile/src/less/icons-material.less +++ b/apps/presentationeditor/mobile/src/less/icons-material.less @@ -434,24 +434,6 @@ .encoded-svg-background(''); } - &.icon-cut { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - - &.icon-copy { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - - &.icon-paste { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - //Comments &.icon-menu-comment { width: 24px; 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/appOptions.js b/apps/presentationeditor/mobile/src/store/appOptions.js index 166ea204f..c68baecff 100644 --- a/apps/presentationeditor/mobile/src/store/appOptions.js +++ b/apps/presentationeditor/mobile/src/store/appOptions.js @@ -4,12 +4,14 @@ export class storeAppOptions { constructor() { makeObservable(this, { isEdit: observable, + canViewComments: observable, setConfigOptions: action, setPermissionOptions: action }); } isEdit = false; + canViewComments = false; config = {}; setConfigOptions (config) { 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/presentationeditor/mobile/src/view/settings/PresentationAbout.jsx b/apps/presentationeditor/mobile/src/view/settings/PresentationAbout.jsx deleted file mode 100644 index 82a36a6d2..000000000 --- a/apps/presentationeditor/mobile/src/view/settings/PresentationAbout.jsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { Fragment } from 'react'; -import { observer, inject } from "mobx-react"; -import { Page, Navbar, Link } from "framework7-react"; -import { useTranslation } from "react-i18next"; - -const PagePresentationAbout = props => { - const { t } = useTranslation(); - const _t = t("View.Settings", { returnObjects: true }); - const store = props.storeAppOptions; - const isCanBranding = store.canBranding; - const licInfo = isCanBranding ? store.customization : null; - const customer = licInfo ? licInfo.customer : null; - const nameCustomer = customer ? customer.name : null; - const mailCustomer = customer ? customer.mail : null; - const addressCustomer = customer ? customer.address : null; - const urlCustomer = customer ? customer.www : null; - const infoCustomer = customer ? customer.info : null; - const logoCustomer = customer ? customer.logo : null; - - const publisherUrl = __PUBLISHER_URL__, - publisherPrintUrl = publisherUrl.replace(/https?:\/{2}|\/$/,""); - return ( - - -
- {licInfo && typeof licInfo == 'object' && typeof(customer)=='object' ? null : ( - - )} - {logoCustomer && logoCustomer.length ? ( - - ) : null} -
-
-

PRESENTATION EDITOR

-

{_t.textVersion} {__PRODUCT_VERSION__}

-
-
-

- - {__PUBLISHER_ADDRESS__} -

-

- - {__SUPPORT_EMAIL__} -

-

- - {__PUBLISHER_PHONE__} -

-

- {publisherPrintUrl} -

- {/*

*/} -
-
- {nameCustomer && nameCustomer.length ? ( -

{nameCustomer}

- ) : null} - {addressCustomer && addressCustomer.length ? ( -

- - {addressCustomer} -

- ) : null} - {mailCustomer && mailCustomer.length ? ( -

- - {mailCustomer} -

- ) : null} - {licInfo && typeof licInfo == 'object' && typeof(customer)=='object' ? null : ( -

- - +371 633-99867 -

- )} - {urlCustomer && urlCustomer.length ? ( -

- - {urlCustomer} - -

- ) : null} - {infoCustomer && infoCustomer.length ? ( -

- -

- ) : null} -
- {licInfo && typeof licInfo == 'object' && typeof(customer)=='object' ? ( -
-
-

- -

-

Ascensio System SIA

-

- www.onlyoffice.com -

-
- ) : null} -
- ); -}; - -const PresentationAbout = inject("storeAppOptions")(observer(PagePresentationAbout)); - -export default PresentationAbout; \ No newline at end of file diff --git a/apps/presentationeditor/mobile/src/view/settings/Settings.jsx b/apps/presentationeditor/mobile/src/view/settings/Settings.jsx index 7f36baaf5..fafbf0711 100644 --- a/apps/presentationeditor/mobile/src/view/settings/Settings.jsx +++ b/apps/presentationeditor/mobile/src/view/settings/Settings.jsx @@ -9,7 +9,8 @@ import DownloadController from "../../controller/settings/Download"; import PresentationInfoController from "../../controller/settings/PresentationInfo"; import PresentationSettingsController from "../../controller/settings/PresentationSettings"; import { PresentationColorSchemes } from "./PresentationSettings"; -import PresentationAboutController from '../../controller/settings/PresentationAbout'; +// import PresentationAboutController from '../../controller/settings/PresentationAbout'; +import About from '../../../../../common/mobile/lib/view/About'; const routes = [ { @@ -42,7 +43,7 @@ const routes = [ }, { path: '/about/', - component: PresentationAboutController + component: About } /*{ path: '/presentation-settings/', diff --git a/apps/spreadsheeteditor/mobile/locale/en.json b/apps/spreadsheeteditor/mobile/locale/en.json index 2d9626f50..e8781f41a 100644 --- a/apps/spreadsheeteditor/mobile/locale/en.json +++ b/apps/spreadsheeteditor/mobile/locale/en.json @@ -6,6 +6,29 @@ "textAnonymous": "Anonymous" } }, + "ContextMenu": { + "menuViewComment": "View Comment", + "menuAddComment": "Add Comment", + "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", + "warnMergeLostData": "Operation can destroy data in the selected cells. Continue?", + "notcriticalErrorTitle": "Warning", + "menuAddLink": "Add Link", + "menuOpenLink": "Open Link", + "menuUnfreezePanes": "Unfreeze Panes", + "menuFreezePanes": "Freeze Panes", + "menuUnwrap": "Unwrap", + "menuWrap": "Wrap", + "menuUnmerge": "Unmerge", + "menuCell": "Cell", + "menuShow": "Show", + "menuHide": "Hide", + "menuEdit": "Edit", + "menuDelete": "Delete" + }, "View" : { "Add" : { "textChart": "Chart", @@ -46,7 +69,8 @@ "textInsert": "Insert", "textInvalidRange": "ERROR! Invalid cells range", "textSortAndFilter": "Sort and Filter", - "textFilter": "Filter" + "textFilter": "Filter", + "textComment": "Comment" }, "Edit" : { "textSelectObjectToEdit": "Select object to edit", @@ -310,6 +334,25 @@ "textReplaceAll": "Replace All" } }, + "Statusbar": { + "textDuplicate": "Duplicate", + "textDelete": "Delete", + "textHide": "Hide", + "textUnhide": "Unhide", + "textErrorLastSheet": "Workbook must have at least one visible worksheet.", + "textErrorRemoveSheet": "Can\"t delete the worksheet.", + "textWarnDeleteSheet": "The worksheet maybe has data. Proceed operation?", + "textSheet": "Sheet", + "textRename": "Rename", + "textErrNameExists": "Worksheet with such name already exist.", + "textErrNameWrongChar": "A sheet name cannot contains characters: \\, \/, *, ?, [, ], :", + "textErrNotEmpty": "Sheet name must not be empty", + "textRenameSheet": "Rename Sheet", + "textSheetName": "Sheet Name", + "textCancel": "Cancel", + "notcriticalErrorTitle": "Warning", + "textMore": "More" + }, "Common": { "ThemeColorPalette": { "textThemeColors": "Theme Colors", @@ -321,7 +364,30 @@ "textBack": "Back", "textUsers": "Users", "textEditUser": "Users who are editing the file:", - "textComments": "Comments" + "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" } + }, + "About": { + "textAbout": "About", + "textVersion": "Version", + "textEmail": "Email", + "textAddress": "Address", + "textTel": "Tel", + "textPoweredBy": "Powered By", + "textBack": "Back" } } 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/ContextMenu.jsx b/apps/spreadsheeteditor/mobile/src/controller/ContextMenu.jsx new file mode 100644 index 000000000..39dad5fe2 --- /dev/null +++ b/apps/spreadsheeteditor/mobile/src/controller/ContextMenu.jsx @@ -0,0 +1,385 @@ +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, + storeSheets: stores.sheets +})) +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) { + const { t } = this.props; + const _t = t("ContextMenu", { returnObjects: true }); + + super.onMenuItemClick(action); + + const api = Common.EditorApi.get(); + const info = api.asc_getCellInfo(); + switch (action) { + case 'cut': + if (!api.asc_Cut() && !LocalStorage.getBool("sse-hide-copy-cut-paste-warning")) { + this.showCopyCutPasteModal(); + } + break; + case 'copy': + if (!api.asc_Copy() && !LocalStorage.getBool("sse-hide-copy-cut-paste-warning")) { + this.showCopyCutPasteModal(); + } + break; + case 'paste': + if (!api.asc_Paste() && !LocalStorage.getBool("sse-hide-copy-cut-paste-warning")) { + this.showCopyCutPasteModal(); + } + break; + case 'addcomment': + Common.Notifications.trigger('addcomment'); + break; + case 'viewcomment': + Common.Notifications.trigger('viewcomment'); + break; + case 'del': + api.asc_emptyCells(Asc.c_oAscCleanOptions.All); + break; + case 'wrap': + api.asc_setCellTextWrap(true); + break; + case 'unwrap': + api.asc_setCellTextWrap(false); + break; + case 'edit': + setTimeout(() => { + this.props.openOptions('edit'); + }, 0); + break; + case 'merge': + if (api.asc_mergeCellsDataLost(Asc.c_oAscMergeOptions.Merge)) { + setTimeout(() => { + f7.dialog.confirm(_t.warnMergeLostData, _t.notcriticalErrorTitle, () => { + api.asc_mergeCells(Asc.c_oAscMergeOptions.Merge); + }); + }, 0); + } else { + api.asc_mergeCells(Asc.c_oAscMergeOptions.Merge); + } + break; + case 'unmerge': + api.asc_mergeCells(Asc.c_oAscMergeOptions.None); + break; + case 'hide': + api[info.asc_getSelectionType() == Asc.c_oAscSelectionType.RangeRow ? 'asc_hideRows' : 'asc_hideColumns'](); + break; + case 'show': + api[info.asc_getSelectionType() == Asc.c_oAscSelectionType.RangeRow ? 'asc_showRows' : 'asc_showColumns'](); + break; + case 'addlink': + setTimeout(() => { + this.props.openOptions('add', 'link'); + }, 400) + break; + case 'openlink': + const linkinfo = info.asc_getHyperlink(); + if ( linkinfo.asc_getType() == Asc.c_oAscHyperlinkType.RangeLink ) { + const nameSheet = linkinfo.asc_getSheet(); + const curActiveSheet = api.asc_getActiveWorksheetIndex(); + api.asc_setWorksheetRange(linkinfo); + const {storeSheets} = this.props; + const tab = storeSheets.sheets.find((sheet) => sheet.name === nameSheet); + if (tab) { + const sdkIndex = tab.index; + if (sdkIndex !== curActiveSheet) { + const index = storeSheets.sheets.indexOf(tab); + storeSheets.setActiveWorksheet(index); + Common.Notifications.trigger('sheet:active', sdkIndex); + } + } + } else { + const url = linkinfo.asc_getHyperlinkUrl().replace(/\s/g, "%20"); + api.asc_getUrlType(url) > 0 && this.openLink(url); + } + break; + case 'freezePanes': + api.asc_freezePane(); + 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 newDocumentPage = window.open(url, '_blank'); + + if (newDocumentPage) { + newDocumentPage.focus(); + } + } + + 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, isDisconnected } = this.props; + + const api = Common.EditorApi.get(); + const cellinfo = api.asc_getCellInfo(); + + const itemsIcon = []; + const itemsText = []; + + let iscellmenu, isrowmenu, iscolmenu, isallmenu, ischartmenu, isimagemenu, istextshapemenu, isshapemenu, istextchartmenu; + let iscelllocked = cellinfo.asc_getLocked(); + const seltype = cellinfo.asc_getSelectionType(); + const xfs = cellinfo.asc_getXfs(); + const isComments = cellinfo.asc_getComments().length > 0; //prohibit adding multiple comments in one cell; + + switch (seltype) { + case Asc.c_oAscSelectionType.RangeCells: iscellmenu = true; break; + case Asc.c_oAscSelectionType.RangeRow: isrowmenu = true; break; + case Asc.c_oAscSelectionType.RangeCol: iscolmenu = true; break; + case Asc.c_oAscSelectionType.RangeMax: isallmenu = true; break; + case Asc.c_oAscSelectionType.RangeImage: isimagemenu = true; break; + case Asc.c_oAscSelectionType.RangeShape: isshapemenu = true; break; + case Asc.c_oAscSelectionType.RangeChart: ischartmenu = true; break; + case Asc.c_oAscSelectionType.RangeChartText: istextchartmenu = true; break; + case Asc.c_oAscSelectionType.RangeShapeText: istextshapemenu = true; break; + } + + if (!isEdit) { + if (iscellmenu || istextchartmenu || istextshapemenu) { + itemsIcon.push({ + event: 'copy', + icon: 'icon-copy' + }); + } + if (iscellmenu && cellinfo.asc_getHyperlink()) { + itemsText.push({ + caption: _t.menuOpenLink, + event: 'openlink' + }); + } + if (canViewComments && isComments) { + itemsText.push({ + caption: _t.menuViewComment, + event: 'viewcomment' + }); + } + } else { + + if (!iscelllocked && (isimagemenu || isshapemenu || ischartmenu || istextshapemenu || istextchartmenu)) { + api.asc_getGraphicObjectProps().every((object) => { + if (object.asc_getObjectType() == Asc.c_oAscTypeSelectElement.Image) { + iscelllocked = object.asc_getObjectValue().asc_getLocked(); + } + return !iscelllocked; + }); + } + + if (iscelllocked || api.isCellEdited) { + itemsIcon.push({ + event: 'copy', + icon: 'icon-copy' + }); + + } else { + itemsIcon.push({ + event: 'cut', + icon: 'icon-cut' + }); + itemsIcon.push({ + event: 'copy', + icon: 'icon-copy' + }); + itemsIcon.push({ + event: 'paste', + icon: 'icon-paste' + }); + itemsText.push({ + caption: _t.menuDelete, + event: 'del' + }); + + if (isimagemenu || isshapemenu || ischartmenu || + istextshapemenu || istextchartmenu) { + itemsText.push({ + caption: _t.menuEdit, + event: 'edit' + }); + } else { + if (iscolmenu || isrowmenu) { + itemsText.push({ + caption: _t.menuHide, + event: 'hide' + }); + itemsText.push({ + caption: _t.menuShow, + event: 'show' + }); + } else if (iscellmenu) { + if (!iscelllocked) { + itemsText.push({ + caption: _t.menuCell, + event: 'edit' + }); + } + + if (cellinfo.asc_getMerge() == Asc.c_oAscMergeOptions.None) { + itemsText.push({ + caption: _t.menuMerge, + event: 'merge' + }); + } + + if (cellinfo.asc_getMerge() == Asc.c_oAscMergeOptions.Merge) { + itemsText.push({ + caption: _t.menuUnmerge, + event: 'unmerge' + }); + } + + itemsText.push( + xfs.asc_getWrapText() ? + { + caption: _t.menuUnwrap, + event: 'unwrap' + } : + { + caption: _t.menuWrap, + event: 'wrap' + }); + + if (cellinfo.asc_getHyperlink() && !cellinfo.asc_getMultiselect()) { + itemsText.push({ + caption: _t.menuOpenLink, + event: 'openlink' + }); + } else if (!cellinfo.asc_getHyperlink() && !cellinfo.asc_getMultiselect() && + !cellinfo.asc_getLockText() && !!cellinfo.asc_getText()) { + itemsText.push({ + caption: _t.menuAddLink, + event: 'addlink' + }); + } + } + + itemsText.push({ + caption: api.asc_getSheetViewSettings().asc_getIsFreezePane() ? _t.menuUnfreezePanes : _t.menuFreezePanes, + event: 'freezePanes' + }); + + } + + if (canViewComments) { + if (isComments) { + itemsText.push({ + caption: _t.menuViewComment, + event: 'viewcomment' + }); + } else if (iscellmenu) { + itemsText.push({ + caption: _t.menuAddComment, + event: 'addcomment' + }); + } + } + } + } + + + if ( Device.phone && itemsText.length > 2 ) { + this.extraItems = itemsText.splice(2,itemsText.length, { + caption: _t.menuMore, + event: 'showActionSheet' + }); + } + + return itemsIcon.concat(itemsText); + } + + 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/spreadsheeteditor/mobile/src/controller/Main.jsx b/apps/spreadsheeteditor/mobile/src/controller/Main.jsx index 4f140b7b8..5a1a6e8cb 100644 --- a/apps/spreadsheeteditor/mobile/src/controller/Main.jsx +++ b/apps/spreadsheeteditor/mobile/src/controller/Main.jsx @@ -1,15 +1,22 @@ -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 { onAdvancedOptions } from './settings/Download.jsx'; +import { + AddCommentController, + CommentsController, + EditCommentController, + ViewCommentsController +} from "../../../../common/mobile/lib/controller/collaboration/Comments"; @inject("storeAppOptions", "storeFocusObjects", "storeCellSettings", "storeTextSettings", "storeChartSettings", "storeSpreadsheetSettings", "storeSpreadsheetInfo") class MainController extends Component { constructor(props) { - super(props) + super(props); + window.editorType = 'sse'; } initSdk() { @@ -318,7 +325,15 @@ class MainController extends Component { } render() { - return + return ( + + + + + + + + ) } componentDidMount() { diff --git a/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx b/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx index 81f22e0ed..2110c1d15 100644 --- a/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx +++ b/apps/spreadsheeteditor/mobile/src/controller/Statusbar.jsx @@ -1,30 +1,83 @@ -import React, { useEffect } from 'react'; -import StatusbarView from '../view/Statusbar'; +import React, { Fragment, useEffect, useState } from 'react'; +import {StatusbarView} from '../view/Statusbar'; import { inject } from 'mobx-react'; +import { f7 } from 'framework7-react'; +import { useTranslation } from 'react-i18next'; +import { Device } from '../../../../common/mobile/utils/device'; -const Statusbar = inject('sheets')(props => { - const {sheets} = props; +const Statusbar = inject('sheets', 'storeAppOptions', 'users')(props => { + const {sheets, storeAppOptions, users} = props; + const {t} = useTranslation(); + const _t = t('Statusbar', {returnObjects: true}); + // console.log(props); + + let isEdit = storeAppOptions.isEdit; + let isDisconnected = users.isDisconnected; useEffect(() => { - console.log("status bar did mount"); + const onDocumentReady = () => { + const api = Common.EditorApi.get(); + api.asc_registerCallback('asc_onUpdateTabColor', onApiUpdateTabColor); + api.asc_registerCallback('asc_onWorkbookLocked', onWorkbookLocked); + api.asc_registerCallback('asc_onWorksheetLocked', onWorksheetLocked); + api.asc_registerCallback('asc_onSheetsChanged', onApiSheetsChanged); + api.asc_registerCallback('asc_onHidePopMenu', onApiHideTabContextMenu); + }; + if ( !Common.EditorApi ) { + Common.Notifications.on('document:ready', onDocumentReady); + Common.Notifications.on('document:ready', onApiSheetsChanged); + } else { + onDocumentReady(); + } - Common.Notifications.on('document:ready', onApiSheetsChanged); - Common.Notifications.on('engineCreated', api => { - api.asc_registerCallback('asc_onSheetsChanged', onApiSheetsChanged.bind(api)); - }); + const on_main_view_click = e => { + if(!e.target.closest('.tab.active')) { + f7.popover.close('.document-menu.modal-in', false); + } + }; + + $$('.view-main').on('click', on_main_view_click); + + return () => { + Common.Notifications.off('document:ready', onDocumentReady); + Common.Notifications.off('document:ready', onApiSheetsChanged); + + const api = Common.EditorApi.get(); + api.asc_unregisterCallback('asc_onUpdateTabColor', onApiUpdateTabColor); + api.asc_unregisterCallback('asc_onWorkbookLocked', onWorkbookLocked); + api.asc_unregisterCallback('asc_onWorksheetLocked', onWorksheetLocked); + api.asc_unregisterCallback('asc_onSheetsChanged', onApiSheetsChanged); + api.asc_unregisterCallback('asc_onHidePopMenu', onApiHideTabContextMenu); + + $$('.view-main').off('click', on_main_view_click); + }; }, []); - const onApiSheetsChanged = api => { - console.log('on api sheets changed'); + const onApiHideTabContextMenu = () => { + f7.popover.close('.document-menu.modal-in', false); + } - !api && (api = Common.EditorApi.get()); + const onWorkbookLocked = locked => { + locked ? $$('.idx-btn-addtab').addClass('disabled') : $$('.idx-btn-addtab').removeClass('disabled'); + }; + const onWorksheetLocked = (index, locked) => { + let model = sheets.sheets.find(sheet => sheet.index === index); + if(model && model.locked != locked) + model.locked = locked; + }; + + const onApiSheetsChanged = () => { + // console.log('on api sheets changed'); + + const api = Common.EditorApi.get(); const sheets_count = api.asc_getWorksheetsCount(); const active_index = api.asc_getActiveWorksheetIndex(); - let i = -1, items = []; - while ( ++i < sheets_count ) { + let i = -1, items = [], hiddentems = []; + + while (++i < sheets_count) { const tab = { index : i, active : active_index == i, @@ -34,46 +87,261 @@ const Statusbar = inject('sheets')(props => { color : api.asc_getWorksheetTabColor(i) }; - items.push(tab); + (api.asc_isWorksheetHidden(i) ? hiddentems : items).push(tab); + // items.push(tab); } - sheets.reset(items); - // this.hiddensheets.reset(hiddentems); + sheets.resetSheets(items); + sheets.resetHiddenSheets(hiddentems); + + updateTabsColors(); + }; - // this.updateTabsColors(); + const loadTabColor = sheetindex => { + const api = Common.EditorApi.get(); + let tab = sheets.sheets.find(sheet => sheet.index === sheetindex); + + if (tab) { + setTabLineColor(tab, api.asc_getWorksheetTabColor(sheetindex)); + } + + }; + + const onApiUpdateTabColor = index => { + loadTabColor(index); + }; + + const setTabLineColor = (tab, color) => { + if (tab) { + if (null !== color) { + color = '#' + Common.Utils.ThemeColor.getHexColor(color.get_r(), color.get_g(), color.get_b()); + } else { + color = ''; + } + + if (color.length) { + if (!tab.active) { + color = '0px 4px 0 ' + Common.Utils.RGBColor(color).toRGBA(0.7) + ' inset'; + } else { + color = '0px 4px 0 ' + color + ' inset'; + } + + $$('.sheet-tabs .tab').eq(tab.index).css('box-shadow', color); + } else { + $$('.sheet-tabs .tab').eq(tab.index).css('box-shadow', ''); + } + } + }; + + const updateTabsColors = () => { + const api = Common.EditorApi.get(); + + sheets.sheets.forEach(model => { + setTabLineColor(model, api.asc_getWorksheetTabColor(model.index)); + }); }; const onTabClicked = i => { const model = sheets.at(i); - const api = Common.EditorApi.get(); + api.asc_showWorksheet(model.index); sheets.setActiveWorksheet(i); + + Common.Notifications.trigger('sheet:active', model.index); + }; + + const createSheetName = () => { + const api = Common.EditorApi.get(); + + let items = [], wc = api.asc_getWorksheetsCount(); + while (wc--) { + items.push(api.asc_getWorksheetName(wc).toLowerCase()); + } + + let index = 0, name; + while(++index < 1000) { + name = /*this.strSheet*/ 'Sheet' + index; + if (items.indexOf(name.toLowerCase()) < 0) break; + } + + return name; }; const onAddTabClicked = () => { const api = Common.EditorApi.get(); api.asc_closeCellEditor(); - const createSheetName = () => { - let items = [], wc = api.asc_getWorksheetsCount(); - while (wc--) { - items.push(api.asc_getWorksheetName(wc).toLowerCase()); - } - - let index = 0, name; - while(++index < 1000) { - name = /*this.strSheet*/ 'Sheet' + index; - if (items.indexOf(name.toLowerCase()) < 0) break; - } - - return name; - }; - + createSheetName(); api.asc_addWorksheet(createSheetName()); }; - return + const onTabClick = (i, target) => { + const api = Common.EditorApi.get(); + const model = sheets.at(i); + // console.log(model); + + let opened = $$('.document-menu.modal-in').length; + let index = model.index; + + f7.popover.close('.document-menu.modal-in', false); + + if (index == api.asc_getActiveWorksheetIndex()) { + if (!opened) { + if (!isDisconnected) { + api.asc_closeCellEditor(); + f7.popover.open('#idx-tab-context-menu-popover', target); + } + } + } else { + f7.popover.close('#idx-tab-context-menu-popover', false); + onTabClicked(i); + Common.Notifications.trigger('sheet:active', index); + } + }; + + const deleteWorksheet = () => { + const api = Common.EditorApi.get(); + const visibleSheets = sheets.visibleWorksheets(); + + if (sheets.sheets.length == 1 || visibleSheets.length == 1) { + f7.dialog.alert(_t.textErrorLastSheet, _t.notcriticalErrorTitle); + } else { + f7.dialog.confirm( + _t.textWarnDeleteSheet, + _t.notcriticalErrorTitle, + () => { + if (!api.asc_deleteWorksheet()) { + f7.dialog.alert(_t.textErrorRemoveSheet, _t.notcriticalErrorTitle); + } + } + ); + } + }; + + const renameWorksheet = () => { + const api = Common.EditorApi.get(); + + if (api.asc_getWorksheetsCount() > 0) { + let sindex = api.asc_getActiveWorksheetIndex(); + + if (api.asc_isWorksheetLockedOrDeleted(sindex)) { + return; + } + + let current = api.asc_getWorksheetName(api.asc_getActiveWorksheetIndex()); + + f7.dialog.create({ + title: _t.textRenameSheet, + content: Device.ios ? + '
' : + '
', + buttons: [ + { + text: 'OK', + bold: true, + onClick: function () { + let s = $$('input[name="modal-sheet-name"]').val(), + wc = api.asc_getWorksheetsCount(), items = [], + err = !s.trim().length ? _t.textErrNotEmpty : ((s.length > 2 && s[0] == '"' && s[s.length-1] == '"' || !/[:\\\/\*\?\[\]\']/.test(s)) ? null : _t.textErrNameWrongChar); + if (!err) { + while (wc--) { + if (sindex !== wc) { + items.push(api.asc_getWorksheetName(wc).toLowerCase()); + } + } + if (items) { + let testval = s.toLowerCase(); + for (var i = items.length - 1; i >= 0; --i) { + if (items[i] === testval) { + err = _t.textErrNameExists; + } + } + } + } + if (err) { + f7.dialog.alert(err, _t.notcriticalErrorTitle, () => { + renameWorksheet(); + }); + } else if (s != current) + api.asc_renameWorksheet(s); + } + }, + { + text: _t.textCancel + } + ] + }).open(); + } + }; + + const createCopyName = (orig) => { + const api = Common.EditorApi.get(); + let wc = api.asc_getWorksheetsCount(), names = []; + + while (wc--) { + names.push(api.asc_getWorksheetName(wc).toLowerCase()); + } + + let re = /^(.*)\((\d)\)$/.exec(orig); + let first = re ? re[1] : orig + ' '; + let index = 1, name; + + while(++index < 1000) { + name = first + '(' + index + ')'; + if (names.indexOf(name.toLowerCase()) < 0) break; + } + + return name; + }; + + const hideWorksheet = (hide, index) => { + const api = Common.EditorApi.get(); + const visibleSheets = sheets.visibleWorksheets(); + + if(hide) { + visibleSheets.length == 1 ? + f7.dialog.alert(_t.textErrorLastSheet, _t.notcriticalErrorTitle) : + api['asc_hideWorksheet']([index]); + } else { + f7.popover.close('#idx-hidden-sheets-popover'); + api['asc_showWorksheet'](index); + loadTabColor(index); + } + }; + + const onTabMenu = (event) => { + const api = Common.EditorApi.get(); + let index = sheets.sheets.find(sheet => sheet.active).index; + + f7.popover.close('.document-menu.modal-in', false); + + switch (event) { + case 'del': deleteWorksheet(); break; + case 'hide': hideWorksheet(true, index); break; + case 'ins': api.asc_insertWorksheet(createSheetName()); break; + case 'copy': + let name = createCopyName(api.asc_getWorksheetName(api.asc_getActiveWorksheetIndex())); + api.asc_copyWorksheet(index, name); + break; + case 'ren': renameWorksheet(); break; + case 'unhide': + f7.popover.open('#idx-hidden-sheets-popover', '.active'); + break; + case 'showMore': + f7.actions.open('#idx-tab-menu-actions'); + break; + default: + let _re = /reveal\:(\d+)/.exec(event); + if (_re && !!_re[1]) { + hideWorksheet(false, parseInt(_re[1])); + } + } + }; + + return ( + + ) }); export default Statusbar; \ No newline at end of file diff --git a/apps/spreadsheeteditor/mobile/src/controller/add/AddOther.jsx b/apps/spreadsheeteditor/mobile/src/controller/add/AddOther.jsx index 4e09ae1a0..85ffc2e93 100644 --- a/apps/spreadsheeteditor/mobile/src/controller/add/AddOther.jsx +++ b/apps/spreadsheeteditor/mobile/src/controller/add/AddOther.jsx @@ -18,9 +18,18 @@ class AddOtherController extends Component { } } + hideAddComment () { + const cellinfo = Common.EditorApi.get().asc_getCellInfo(); + const iscelllocked = cellinfo.asc_getLocked(); + const seltype = cellinfo.asc_getSelectionType(); + const isComments = cellinfo.asc_getComments().length > 0; + return (!(seltype === Asc.c_oAscSelectionType.RangeCells && !iscelllocked) || isComments); + } + render () { return ( - ) } diff --git a/apps/spreadsheeteditor/mobile/src/less/app-ios.less b/apps/spreadsheeteditor/mobile/src/less/app-ios.less index a1575794a..e058a2241 100644 --- a/apps/spreadsheeteditor/mobile/src/less/app-ios.less +++ b/apps/spreadsheeteditor/mobile/src/less/app-ios.less @@ -1,5 +1,23 @@ .device-ios { - + input.modal-text-input { + box-sizing: border-box; + height: 26px; + background: #fff; + margin: 0; + margin-top: 15px; + padding: 0 5px; + border: 1px solid rgba(0,0,0,.3); + border-radius: 0; + width: 100%; + font-size: 14px; + font-family: inherit; + display: block; + box-shadow: 0 0 0 transparent; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; + } } diff --git a/apps/spreadsheeteditor/mobile/src/less/app.less b/apps/spreadsheeteditor/mobile/src/less/app.less index 386e8d550..b2dbf1c91 100644 --- a/apps/spreadsheeteditor/mobile/src/less/app.less +++ b/apps/spreadsheeteditor/mobile/src/less/app.less @@ -12,6 +12,8 @@ @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 '../../../../common/mobile/resources/less/comments.less'; @import './app-material.less'; @import './app-ios.less'; @import './icons-ios.less'; diff --git a/apps/spreadsheeteditor/mobile/src/less/icons-ios.less b/apps/spreadsheeteditor/mobile/src/less/icons-ios.less index ded1d72c2..589e9d3c6 100644 --- a/apps/spreadsheeteditor/mobile/src/less/icons-ios.less +++ b/apps/spreadsheeteditor/mobile/src/less/icons-ios.less @@ -370,21 +370,6 @@ height: 24px; .encoded-svg-background(' '); } - &.icon-cut { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - &.icon-copy { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - &.icon-paste { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } //Comments &.icon-menu-comment { width: 30px; diff --git a/apps/spreadsheeteditor/mobile/src/less/icons-material.less b/apps/spreadsheeteditor/mobile/src/less/icons-material.less index 1aad05d4f..c77235c01 100644 --- a/apps/spreadsheeteditor/mobile/src/less/icons-material.less +++ b/apps/spreadsheeteditor/mobile/src/less/icons-material.less @@ -334,21 +334,6 @@ height: 24px; .encoded-svg-background(''); } - &.icon-cut { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - &.icon-copy { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } - &.icon-paste { - width: 24px; - height: 24px; - .encoded-svg-background(''); - } //Comments &.icon-menu-comment { width: 24px; diff --git a/apps/spreadsheeteditor/mobile/src/less/statusbar.less b/apps/spreadsheeteditor/mobile/src/less/statusbar.less index 3ce1c36cd..e2dc8269a 100644 --- a/apps/spreadsheeteditor/mobile/src/less/statusbar.less +++ b/apps/spreadsheeteditor/mobile/src/less/statusbar.less @@ -6,7 +6,6 @@ height: @statusbar-height; min-height: @statusbar-height; background-color: @background-normal; - display: flex; .tab { @@ -26,13 +25,23 @@ } .statusbar--box-tabs { + overflow-x: auto; + overflow-y: hidden; + &::-webkit-scrollbar { + -webkit-appearance: none; + display: none; + // width: 0; + // height: 0; + } > ul { padding: 0; margin: 0; height: 100%; white-space: pre; - overflow: hidden; - + // overflow: hidden; + // position: absolute; + // left: 0; + // top: 0; > li { a { font-size: 12px; diff --git a/apps/spreadsheeteditor/mobile/src/page/app.jsx b/apps/spreadsheeteditor/mobile/src/page/app.jsx index 28fac09c7..b38f5c34a 100644 --- a/apps/spreadsheeteditor/mobile/src/page/app.jsx +++ b/apps/spreadsheeteditor/mobile/src/page/app.jsx @@ -34,9 +34,8 @@ export default class extends React.Component { return ( {/* Your main view, should have "view-main" class */} - - - + + ) } diff --git a/apps/spreadsheeteditor/mobile/src/page/main.jsx b/apps/spreadsheeteditor/mobile/src/page/main.jsx index ef2c0d5f6..2f7b3713d 100644 --- a/apps/spreadsheeteditor/mobile/src/page/main.jsx +++ b/apps/spreadsheeteditor/mobile/src/page/main.jsx @@ -10,8 +10,10 @@ import AddOptions from "../view/add/Add"; import EditOptions from "../view/edit/Edit"; import { Device } from '../../../../common/mobile/utils/device'; import { Search, SearchSettings } from '../controller/Search'; +import { f7 } from 'framework7-react'; import {FunctionGroups} from "../controller/add/AddFunction"; +import ContextMenu from '../controller/ContextMenu'; export default class MainPage extends Component { constructor(props) { @@ -26,6 +28,8 @@ export default class MainPage extends Component { } handleClickToOpenOptions = (opts, showOpts) => { + f7.popover.close('.document-menu.modal-in', false); + this.setState(state => { if ( opts == 'edit' ) return {editOptionsVisible: true}; @@ -70,7 +74,7 @@ export default class MainPage extends Component { this.handleClickToOpenOptions('edit')}> this.handleClickToOpenOptions('add')}> { Device.phone ? null : } - this.handleClickToOpenOptions('coauth')}> + this.handleClickToOpenOptions('coauth')}> this.handleClickToOpenOptions('settings')}> @@ -98,6 +102,7 @@ export default class MainPage extends Component { {/* hidden component*/} +
) } diff --git a/apps/spreadsheeteditor/mobile/src/store/appOptions.js b/apps/spreadsheeteditor/mobile/src/store/appOptions.js index 70d85c02d..bb3f69a2d 100644 --- a/apps/spreadsheeteditor/mobile/src/store/appOptions.js +++ b/apps/spreadsheeteditor/mobile/src/store/appOptions.js @@ -3,13 +3,19 @@ import {action, observable, makeObservable} from 'mobx'; export class storeAppOptions { constructor() { makeObservable(this, { + isEdit: observable, + canViewComments: observable, setConfigOptions: action, setPermissionOptions: action }); } + isEdit = false; config = {}; + isEdit = false; + canViewComments = false; + setConfigOptions (config) { this.config = config; this.user = Common.Utils.fillUserInfo(config.user, config.lang, "Local.User"/*me.textAnonymous*/); diff --git a/apps/spreadsheeteditor/mobile/src/store/mainStore.js b/apps/spreadsheeteditor/mobile/src/store/mainStore.js index c5b523722..0190760f3 100644 --- a/apps/spreadsheeteditor/mobile/src/store/mainStore.js +++ b/apps/spreadsheeteditor/mobile/src/store/mainStore.js @@ -15,6 +15,7 @@ import {storeAppOptions} from "./appOptions"; // import {storeTableSettings} from "./tableSettings"; import {storeChartSettings} from "./chartSettings"; import {storeSpreadsheetSettings} from "./spreadsheetSettings"; +import {storeComments} from "../../../../common/mobile/lib/store/comments"; export const stores = { storeFocusObjects: new storeFocusObjects(), @@ -30,8 +31,9 @@ export const stores = { storeShapeSettings: new storeShapeSettings(), storeChartSettings: new storeChartSettings(), storePalette: new storePalette(), - storeCellSettings: new storeCellSettings() + storeCellSettings: new storeCellSettings(), // storeImageSettings: new storeImageSettings(), // storeTableSettings: new storeTableSettings() + storeComments: new storeComments() }; diff --git a/apps/spreadsheeteditor/mobile/src/store/sheets.js b/apps/spreadsheeteditor/mobile/src/store/sheets.js index c5e5b06ee..b0a592be0 100644 --- a/apps/spreadsheeteditor/mobile/src/store/sheets.js +++ b/apps/spreadsheeteditor/mobile/src/store/sheets.js @@ -21,20 +21,28 @@ class Worksheet { export class storeWorksheets { sheets; + hiddensheets; constructor() { makeObservable(this, { sheets: observable, - reset: action, + hiddensheets: observable, + resetSheets: action, + resetHiddenSheets: action, setActiveWorksheet: action }); this.sheets = []; + this.hiddensheets = []; } - reset(sheets) { + resetSheets(sheets) { this.sheets = Object.values(sheets) } + resetHiddenSheets(hiddensheets) { + this.hiddensheets = Object.values(hiddensheets) + } + setActiveWorksheet(i) { if ( !this.sheets[i].active ) { this.sheets.forEach(model => { @@ -57,4 +65,8 @@ export class storeWorksheets { hiddenWorksheets() { return this.sheets.filter(model => model.hidden); } + + visibleWorksheets() { + return this.sheets.filter(model => !model.hidden); + } } diff --git a/apps/spreadsheeteditor/mobile/src/view/Statusbar.jsx b/apps/spreadsheeteditor/mobile/src/view/Statusbar.jsx index 666c3ebb5..d0d7fea21 100644 --- a/apps/spreadsheeteditor/mobile/src/view/Statusbar.jsx +++ b/apps/spreadsheeteditor/mobile/src/view/Statusbar.jsx @@ -1,18 +1,100 @@ -import React from 'react'; -import { View, Toolbar, Link, Icon } from 'framework7-react'; +import React, { Fragment } from 'react'; +import { View, Toolbar, Link, Icon, Popover, List, ListButton, Actions, ActionsGroup, ActionsButton } from 'framework7-react'; import { observer, inject } from "mobx-react"; +import { useTranslation } from 'react-i18next'; +import { Device } from '../../../../common/mobile/utils/device'; const viewStyle = { height: 30 }; const StatusbarView = inject('sheets')(observer(props => { + const { t } = useTranslation(); + const _t = t('Statusbar', {returnObjects: true}); + const isAndroid = Device.android; + const isPhone = Device.isPhone; const { sheets } = props; + const hiddenSheets = sheets.hiddensheets; + const getTabClassList = model => `tab ${model.active ? 'active' : ''} ${model.locked ? 'locked' : ''}`; + // const $boxTabs = $$('.sheet-tabs'); + // const $statusBar = $$('.statusbar'); - const getTabClassList = model => - `tab ${model.active ? 'active':''} ${model.locked ? 'locked':''}`; + // $boxTabs.on('touchstart', onTouchStart); + // $boxTabs.on('touchmove', onTouchMove); + // $boxTabs.on('touchend', onTouchEnd); - return + // let touch = {}; + + // function hasInvisible() { + // let _left_bound_ = $boxTabs.offset().left, + // _right_bound_ = $boxTabs.width() + _left_bound_ - $statusBar.width(); + // // _right_bound_ = _left_bound_ + $boxTabs.width(); + + // // console.log(_left_bound_); + // console.log(_right_bound_); + + // let tab = $$('.sheet-tabs li')[0]; + // let rect = tab.getBoundingClientRect(); + + // if (!(rect.left < _left_bound_)) { + // // tab = $$('.sheet-tabs li')[$$('.sheet-tabs li').length - 1]; + // // rect = tab.getBoundingClientRect(); + + // // if (!((rect.right).toFixed(2) > _right_bound_)) + // // return false; + // if(_right_bound_ <= 0) { + // return false; + // } + // } + + // return true; + // } + + // function onTouchStart(e) { + // if (hasInvisible()) { + // console.log(e); + // let touches = e.changedTouches; + // touch.startx = touches[0].clientX; + // touch.scrollx = $boxTabs.scrollLeft(); + // // console.log(touch.scrollx); + + // touch.timer = setTimeout(function () { + // // touch.longtouch = true; + // }, 500); + // // e.preventDefault(); + // } + // } + + // function onTouchMove(e) { + // if (touch.startx !== undefined) { + // // console.log(e); + // let touches = e.changedTouches; + + // if (touch.longtouch) {} + // else { + // if (touch.timer) clearTimeout(touch.timer), delete touch.timer; + // let valueLeft = touch.scrollx + (touch.startx - touches[0].clientX); + // console.log(valueLeft); + // // $boxTabs.scrollLeft(valueLeft); + // + // } + + // // e.preventDefault(); + // } + // } + + // function onTouchEnd(e) { + // if (touch.startx !== undefined) { + // // console.log(e); + // touch.longtouch = false; + // delete touch.startx; + // // e.preventDefault(); + // } + // } + + return ( + +
props.onAddTabClicked()}> @@ -22,13 +104,70 @@ const StatusbarView = inject('sheets')(observer(props => {
-
; +
+ + {isPhone || isAndroid ? ( + + props.onTabMenu('copy')} /> + props.onTabMenu('del')} /> + props.onTabMenu('showMore')} /> + + ) : ( + + props.onTabMenu('copy')} /> + props.onTabMenu('del')} /> + props.onTabMenu('ren')} /> + props.onTabMenu('hide')} /> + {hiddenSheets.length ? ( + props.onTabMenu('unhide')} /> + ) : null} + + )} + + {isPhone || isAndroid ? ( + + + props.onTabMenu('ren')}>{_t.textRename} + props.onTabMenu('hide')}>{_t.textHide} + {hiddenSheets.length ? ( + props.onTabMenu('unhide')}>{_t.textUnhide} + ) : null} + + + {_t.textCancel} + + + ) : null} + {hiddenSheets.length ? ( + + + {hiddenSheets.map(sheet => { + return ( + props.onTabMenu(`reveal:${sheet.index}`)} /> + ) + })} + + + ) : null} + + ) })); -export default StatusbarView; +export {StatusbarView}; diff --git a/apps/spreadsheeteditor/mobile/src/view/add/AddOther.jsx b/apps/spreadsheeteditor/mobile/src/view/add/AddOther.jsx index 06727ee2a..0bd00a652 100644 --- a/apps/spreadsheeteditor/mobile/src/view/add/AddOther.jsx +++ b/apps/spreadsheeteditor/mobile/src/view/add/AddOther.jsx @@ -5,11 +5,18 @@ import { useTranslation } from 'react-i18next'; const AddOther = props => { const { t } = useTranslation(); const _t = t('View.Add', {returnObjects: true}); + const hideAddComment = props.hideAddComment(); return ( + {!hideAddComment && { + props.closeModal(); + Common.Notifications.trigger('addcomment'); + }}> + + } diff --git a/apps/spreadsheeteditor/mobile/src/view/settings/Settings.jsx b/apps/spreadsheeteditor/mobile/src/view/settings/Settings.jsx index 485eb9bf8..f1a3ba84e 100644 --- a/apps/spreadsheeteditor/mobile/src/view/settings/Settings.jsx +++ b/apps/spreadsheeteditor/mobile/src/view/settings/Settings.jsx @@ -9,7 +9,8 @@ import SpreadsheetInfoController from '../../controller/settings/SpreadsheetInfo import {DownloadWithTranslation} from '../../controller/settings/Download.jsx'; import {SpreadsheetColorSchemes, SpreadsheetFormats, SpreadsheetMargins} from './SpreadsheetSettings.jsx'; import {MacrosSettings, RegionalSettings, FormulaLanguage} from './ApplicationSettings.jsx'; -import SpreadsheetAbout from './SpreadsheetAbout.jsx'; +// import SpreadsheetAbout from './SpreadsheetAbout.jsx'; +import About from '../../../../../common/mobile/lib/view/About'; const routes = [ { @@ -57,8 +58,8 @@ const routes = [ component: SpreadsheetInfoController }, { - path: '/spreadsheet-about/', - component: SpreadsheetAbout + path: '/about/', + component: About } ]; @@ -138,7 +139,7 @@ const SettingsList = withTranslation()(props => { - + diff --git a/vendor/framework7-react/build/webpack.config.js b/vendor/framework7-react/build/webpack.config.js index 42acab398..235a56317 100644 --- a/vendor/framework7-react/build/webpack.config.js +++ b/vendor/framework7-react/build/webpack.config.js @@ -4,9 +4,10 @@ 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'); +const fs = require('fs') const path = require('path'); @@ -18,6 +19,7 @@ const env = process.env.NODE_ENV || 'development'; const target = process.env.TARGET || 'web'; const editor = process.env.TARGET_EDITOR == 'cell' ? 'spreadsheeteditor' : process.env.TARGET_EDITOR == 'slide' ? 'presentationeditor' : 'documenteditor'; +const targetPatch = process.env.TARGET_EDITOR || 'word'; module.exports = { mode: env, @@ -53,6 +55,7 @@ module.exports = { minimizer: [new TerserPlugin({ sourceMap: true, })], + moduleIds: 'named', }, module: { rules: [ @@ -163,8 +166,8 @@ module.exports = { }), ...(env === 'production' ? [ - new OptimizeCSSPlugin({ - cssProcessorOptions: { + new CssMinimizerPlugin({ + processorOptions: { safe: true, map: { inline: false }, }, @@ -173,7 +176,7 @@ module.exports = { ] : [ // Development only plugins new webpack.HotModuleReplacementPlugin(), - new webpack.NamedModulesPlugin(), + // new webpack.NamedModulesPlugin(), ]), // new CleanWebpackPlugin(), new HtmlWebpackPlugin({ @@ -206,5 +209,10 @@ module.exports = { }, ], }), + new webpack.NormalModuleReplacementPlugin( + /\.{2}\/lib\/patch/, + resource => fs.existsSync(`../../../web-apps-mobile/${targetPatch}/patch.jsx`) ? + resource.request = `../../../../../../web-apps-mobile/${targetPatch}/patch.jsx` : resource + ), ], }; \ No newline at end of file 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" } }