diff --git a/apps/common/mobile/resources/less/common-material.less b/apps/common/mobile/resources/less/common-material.less index da9496959..119f9e0f6 100644 --- a/apps/common/mobile/resources/less/common-material.less +++ b/apps/common/mobile/resources/less/common-material.less @@ -18,6 +18,7 @@ --f7-navbar-text-color: @fill-white; --f7-navbar-height: 56px; + --f7-list-bg-color: @background-primary; --f7-subnavbar-bg-color: @toolbar-background; --f7-subnavbar-link-color: @toolbar-icons; --f7-subnavbar-text-color: @fill-white; @@ -84,7 +85,6 @@ --f7-input-placeholder-color: @text-secondary; --f7-label-text-color: @text-normal; --f7-page-bg-color: @background-tertiary; - --f7-list-bg-color: @background-primary; --f7-list-item-border-color: @background-menu-divider; --f7-list-chevron-icon-color: @text-tertiary; --f7-toggle-inactive-color: @background-menu-divider; diff --git a/apps/documenteditor/mobile/locale/en.json b/apps/documenteditor/mobile/locale/en.json index a1e96ec40..1558e4ec9 100644 --- a/apps/documenteditor/mobile/locale/en.json +++ b/apps/documenteditor/mobile/locale/en.json @@ -57,7 +57,10 @@ "textStartAt": "Start At", "textTable": "Table", "textTableSize": "Table Size", - "txtNotUrl": "This field should be a URL in the format \"http://www.example.com\"" + "txtNotUrl": "This field should be a URL in the format \"http://www.example.com\"", + "textTableContents": "Table of Contents", + "textWithPageNumbers": "With Page Numbers", + "textWithBlueLinks": "With Blue Links" }, "Common": { "Collaboration": { @@ -193,7 +196,9 @@ "textDoNotShowAgain": "Don't show again", "textNumberingValue": "Numbering Value", "textOk": "OK", - "textRows": "Rows" + "textRows": "Rows", + "textRefreshEntireTable": "Refresh entire table", + "textRefreshPageNumbersOnly": "Refresh page numbers only" }, "Edit": { "notcriticalErrorTitle": "Warning", @@ -330,7 +335,29 @@ "textTu": "Tu", "textType": "Type", "textWe": "We", - "textWrap": "Wrap" + "textWrap": "Wrap", + "textTableOfCont": "TOC", + "textPageNumbers": "Page Numbers", + "textSimple": "Simple", + "textRightAlign": "Right Align", + "textLeader": "Leader", + "textStructure": "Structure", + "textRefresh": "Refresh", + "textLevels": "Levels", + "textRemoveTableContent": "Remove table of content", + "textCurrent": "Current", + "textOnline": "Online", + "textClassic": "Classic", + "textDistinctive": "Distinctive", + "textCentered": "Centered", + "textFormal": "Formal", + "textStandard": "Standard", + "textModern": "Modern", + "textCancel": "Cancel", + "textRefreshEntireTable": "Refresh entire table", + "textRefreshPageNumbersOnly": "Refresh page numbers only", + "textStyles": "Styles", + "textAmountOfLevels": "Amount of Levels" }, "Error": { "convertationTimeoutText": "Conversion timeout exceeded.", @@ -616,7 +643,11 @@ "txtScheme6": "Concourse", "txtScheme7": "Equity", "txtScheme8": "Flow", - "txtScheme9": "Foundry" + "txtScheme9": "Foundry", + "textNavigation": "Navigation", + "textEmptyScreens": "There are no headings in the document. Apply a headings style to the text so that it appeas in the table of cotents.", + "textBeginningDocument": "Beginning of document", + "textEmptyHeading": "Empty Heading" }, "Toolbar": { "dlgLeaveMsgText": "You have unsaved changes. Click 'Stay on this Page' to wait for autosave. Click 'Leave this Page' to discard all the unsaved changes.", diff --git a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx index 45906ad60..5865e0b11 100644 --- a/apps/documenteditor/mobile/src/controller/ContextMenu.jsx +++ b/apps/documenteditor/mobile/src/controller/ContextMenu.jsx @@ -114,9 +114,26 @@ class ContextMenu extends ContextMenuController { this.props.openOptions('coauth', 'cm-review-change'); }, 400); break; + case 'refreshEntireTable': + this.onTableContentsUpdate('all'); + break; + case 'refreshPageNumbers': + this.onTableContentsUpdate('pages'); + break; } } + onTableContentsUpdate(type, currentTOC) { + const api = Common.EditorApi.get(); + let props = api.asc_GetTableOfContentsPr(currentTOC); + + if (props) { + if (currentTOC && props) + currentTOC = props.get_InternalClass(); + api.asc_UpdateTableOfContents(type == 'pages', currentTOC); + } + }; + showCopyCutPasteModal() { const { t } = this.props; const _t = t("ContextMenu", { returnObjects: true }); @@ -223,6 +240,7 @@ class ContextMenu extends ContextMenuController { const { canViewComments, canCoAuthoring, canComments } = this.props; const api = Common.EditorApi.get(); + const inToc = api.asc_GetTableOfContentsPr(true); const stack = api.getSelectedElements(); const canCopy = api.can_CopyCut(); @@ -294,6 +312,17 @@ class ContextMenu extends ContextMenuController { }); } + if(inToc) { + itemsText.push({ + caption: t('ContextMenu.textRefreshEntireTable'), + event: 'refreshEntireTable' + }); + itemsText.push({ + caption: t('ContextMenu.textRefreshPageNumbersOnly'), + event: 'refreshPageNumbers' + }); + } + return itemsIcon.concat(itemsText); } } diff --git a/apps/documenteditor/mobile/src/controller/Error.jsx b/apps/documenteditor/mobile/src/controller/Error.jsx index 16eca5427..4373e04d0 100644 --- a/apps/documenteditor/mobile/src/controller/Error.jsx +++ b/apps/documenteditor/mobile/src/controller/Error.jsx @@ -4,6 +4,9 @@ import { f7 } from 'framework7-react'; import { useTranslation } from 'react-i18next'; const ErrorController = inject('storeAppOptions')(({storeAppOptions, LoadingDocument}) => { + const { t } = useTranslation(); + const _t = t("Error", { returnObjects: true }); + useEffect(() => { const on_engine_created = k => { k.asc_registerCallback('asc_onError', onError); }; @@ -20,9 +23,6 @@ const ErrorController = inject('storeAppOptions')(({storeAppOptions, LoadingDocu }); const onError = (id, level, errData) => { - const {t} = useTranslation(); - const _t = t("Error", { returnObjects: true }); - if (id === Asc.c_oAscError.ID.LoadingScriptError) { f7.notification.create({ title: _t.criticalErrorTitle, diff --git a/apps/documenteditor/mobile/src/controller/add/AddTableContents.jsx b/apps/documenteditor/mobile/src/controller/add/AddTableContents.jsx new file mode 100644 index 000000000..77b18f6f7 --- /dev/null +++ b/apps/documenteditor/mobile/src/controller/add/AddTableContents.jsx @@ -0,0 +1,65 @@ +import React, {Component} from 'react'; +import { f7 } from 'framework7-react'; +import {Device} from '../../../../../common/mobile/utils/device'; +import {AddTableContents} from '../../view/add/AddTableContents'; + +class AddTableContentsController extends Component { + constructor (props) { + super(props); + this.onTableContents = this.onTableContents.bind(this); + } + + closeModal () { + if ( Device.phone ) { + f7.sheet.close('.add-popup', true); + } else { + f7.popover.close('#add-popover'); + } + } + + componentDidMount () { + const api = Common.EditorApi.get(); + const widthImage = !Device.phone ? 330 : window.innerWidth - 30; + api.asc_getButtonsTOC('toc1', 'toc2', widthImage); + } + + onTableContents(type, currentTOC) { + const api = Common.EditorApi.get(); + let props = api.asc_GetTableOfContentsPr(currentTOC); + + switch (type) { + case 0: + if (!props) { + props = new Asc.CTableOfContentsPr(); + props.put_OutlineRange(1, 9); + } + props.put_Hyperlink(true); + props.put_ShowPageNumbers(true); + props.put_RightAlignTab(true); + props.put_TabLeader(Asc.c_oAscTabLeader.Dot); + api.asc_AddTableOfContents(null, props); + break; + case 1: + if (!props) { + props = new Asc.CTableOfContentsPr(); + props.put_OutlineRange(1, 9); + } + props.put_Hyperlink(true); + props.put_ShowPageNumbers(false); + props.put_TabLeader(Asc.c_oAscTabLeader.None); + props.put_StylesType(Asc.c_oAscTOCStylesType.Web); + api.asc_AddTableOfContents(null, props); + break; + } + + this.closeModal(); + } + + render () { + return ( + + ) + } +} + +export default AddTableContentsController; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/controller/edit/EditTableContents.jsx b/apps/documenteditor/mobile/src/controller/edit/EditTableContents.jsx new file mode 100644 index 000000000..584a5c196 --- /dev/null +++ b/apps/documenteditor/mobile/src/controller/edit/EditTableContents.jsx @@ -0,0 +1,277 @@ +import React, {Component} from 'react'; +import { f7 } from 'framework7-react'; +import {Device} from '../../../../../common/mobile/utils/device'; +import {observer, inject} from "mobx-react"; +import { EditTableContents } from '../../view/edit/EditTableContents'; + +class EditTableContentsController extends Component { + constructor (props) { + super(props); + + this.startLevel = 1; + this.endLevel = 3; + this.fillTOCProps = this.fillTOCProps.bind(this); + this.onRemoveTableContents = this.onRemoveTableContents.bind(this); + this.onUpdateTableContents = this.onUpdateTableContents.bind(this); + } + + closeModal() { + if (Device.phone) { + f7.sheet.close('#edit-sheet', true); + } else { + f7.popover.close('#edit-popover'); + } + } + + getStylesImages() { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + const arrValuesStyles = [Asc.c_oAscTOCStylesType.Simple, Asc.c_oAscTOCStylesType.Web, Asc.c_oAscTOCStylesType.Standard, Asc.c_oAscTOCStylesType.Modern, Asc.c_oAscTOCStylesType.Classic]; + + arrValuesStyles.forEach((value, index) => { + let canvasElem = document.querySelector(`#image-toc-style${index}`); + + propsTableContents.put_StylesType(value); + api.SetDrawImagePlaceContents(canvasElem, propsTableContents); + }); + } + + onStyle(value) { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + + propsTableContents.put_StylesType(value); + api.asc_SetTableOfContentsPr(propsTableContents); + } + + onPageNumbers(value) { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + + propsTableContents.put_ShowPageNumbers(value); + api.asc_SetTableOfContentsPr(propsTableContents); + } + + onRightAlign(value) { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + + propsTableContents.put_RightAlignTab(value); + api.asc_SetTableOfContentsPr(propsTableContents); + } + + onLeader(value) { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + + propsTableContents.put_TabLeader(value); + api.asc_SetTableOfContentsPr(propsTableContents); + } + + onLevelsChange(value) { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + + propsTableContents.clear_Styles(); + propsTableContents.put_OutlineRange(1, value); + api.asc_SetTableOfContentsPr(propsTableContents); + } + + fillTOCProps(props) { + const api = Common.EditorApi.get(); + const docStyles = api.asc_GetStylesArray(); + + let checkStyles = false, + disableOutlines = false, + styles = []; + + docStyles.forEach(style => { + let name = style.get_Name(), + level = api.asc_GetHeadingLevel(name); + + if (style.get_QFormat() || level >= 0) { + styles.push({ + name: name, + displayValue: style.get_TranslatedName(), + allowSelected: false, + checked: false, + value: '', + headerLevel: (level >= 0) ? level + 1 : -1 + }) + } + }); + + if(props) { + let start = props.get_OutlineStart(), + end = props.get_OutlineEnd(), + count = props.get_StylesCount(); + + this.startLevel = start; + this.endLevel = end; + this.count = count; + + if ((start < 0 || end < 0) && count < 1) { + start = 1; + end = 9; + } + + for (let i = 0; i < count; i++) { + let styleName = props.get_StyleName(i), + level = props.get_StyleLevel(i), + rec = styles.find(style => style.name == styleName); + + if (rec) { + rec.checked = true; + rec.value = level; + if (rec.headerLevel !== level) + disableOutlines = true; + } else { + styles.push({ + name: styleName, + displayValue: styleName, + allowSelected: false, + checked: true, + value: level, + headerLevel: -1 + }); + + disableOutlines = true; + } + } + + if (start > 0 && end > 0) { + for (let i = start; i <= end; i++) { + let rec = styles.find(style => style.headerLevel === i); + + if (rec) { + rec.checked = true; + rec.value = i; + } + } + } + + let newStart = -1, + newEnd = -1, + emptyIndex = -1; + + for (let i = 0; i < 9; i++) { + let rec = styles.find(style => style.headerLevel === i + 1); + + if (rec) { + let headerLevel = rec.headerLevel, + level = rec.value; + + if (headerLevel == level) { + if (emptyIndex < 1) { + if (newStart < 1) + newStart = level; + newEnd = level; + } else { + newStart = newEnd = -1; + disableOutlines = true; + break; + } + } else if (!rec.checked) { + (newStart > 0) && (emptyIndex = i + 1); + } else { + newStart = newEnd = -1; + disableOutlines = true; + break; + } + } + } + + checkStyles = (disableOutlines || newStart > 1); + } else { + for (let i = this.startLevel; i <= this.endLevel; i++) { + let rec = styles.find(style => style.headerLevel === i); + + if (rec) { + rec.checked = true; + rec.value = i; + } + } + } + + styles.sort(function(a, b) { + let aname = a.displayValue.toLocaleLowerCase(), + bname = b.displayValue.toLocaleLowerCase(); + if (aname < bname) return -1; + if (aname > bname) return 1; + return 0; + }); + + return { + styles, + // start: this.startLevel, + end: this.endLevel, + count: this.count, + // disableOutlines, + // checkStyles + } + } + + addStyles(styles, styleName, value) { + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + + propsTableContents.clear_Styles(); + + styles.forEach(style => { + if(style.name === styleName) { + propsTableContents.add_Style(styleName, value); + } else { + propsTableContents.add_Style(style.name, style.value); + } + }); + + if (propsTableContents.get_StylesCount() > 0) { + propsTableContents.put_OutlineRange(-1, -1); + } else { + propsTableContents.put_OutlineRange(1, 9); + } + + api.asc_SetTableOfContentsPr(propsTableContents); + } + + onUpdateTableContents(type, currentTOC) { + const api = Common.EditorApi.get(); + let props = api.asc_GetTableOfContentsPr(currentTOC); + + if (props) { + if (currentTOC && props) + currentTOC = props.get_InternalClass(); + api.asc_UpdateTableOfContents(type == 'pages', currentTOC); + } + }; + + onRemoveTableContents(currentTOC) { + const api = Common.EditorApi.get(); + currentTOC = !!currentTOC; + let props = api.asc_GetTableOfContentsPr(currentTOC); + + currentTOC = (currentTOC && props) ? props.get_InternalClass() : undefined; + api.asc_RemoveTableOfContents(currentTOC); + + this.closeModal(); + } + + render () { + return ( + + ) + } +} + +export default EditTableContentsController; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/controller/settings/Navigation.jsx b/apps/documenteditor/mobile/src/controller/settings/Navigation.jsx new file mode 100644 index 000000000..71366fca3 --- /dev/null +++ b/apps/documenteditor/mobile/src/controller/settings/Navigation.jsx @@ -0,0 +1,83 @@ +import React, { Component } from "react"; +import { NavigationPopover, NavigationSheet } from "../../view/settings/Navigation"; +import { Device } from '../../../../../common/mobile/utils/device'; +import { withTranslation } from 'react-i18next'; + +class NavigationController extends Component { + constructor(props) { + super(props); + this.updateNavigation = this.updateNavigation.bind(this); + } + + updateNavigation() { + const api = Common.EditorApi.get(); + const navigationObject = api.asc_ShowDocumentOutline(); + + if (!navigationObject) return; + + const count = navigationObject.get_ElementsCount(); + const { t } = this.props; + + let arrHeaders = [], + prevLevel = -1, + headerLevel = -1, + firstHeader = !navigationObject.isFirstItemNotHeader(); + + for (let i = 0; i < count; i++) { + let level = navigationObject.get_Level(i), + hasParent = true; + if (level > prevLevel && i > 0) + arrHeaders[i - 1]['hasSubItems'] = true; + if (headerLevel < 0 || level <= headerLevel) { + if (i > 0 || firstHeader) + headerLevel = level; + hasParent = false; + } + + arrHeaders.push({ + name: navigationObject.get_Text(i), + level, + index: i, + hasParent, + isEmptyItem: navigationObject.isEmptyItem(i) + }); + + prevLevel = level; + } + + if (count > 0 && !firstHeader) { + arrHeaders[0]['hasSubItems'] = false; + arrHeaders[0]['isNotHeader'] = true; + arrHeaders[0]['name'] = t('Settings.textBeginningDocument'); + } + + return arrHeaders; + } + + onSelectItem(index) { + const api = Common.EditorApi.get(); + const navigationObject = api.asc_ShowDocumentOutline(); + + if (navigationObject) { + navigationObject.goto(index); + } + }; + + render() { + return ( + !Device.phone ? + + : + + ); + } +} + +export default withTranslation()(NavigationController); \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/less/app-ios.less b/apps/documenteditor/mobile/src/less/app-ios.less index d8f784511..74ddb7925 100644 --- a/apps/documenteditor/mobile/src/less/app-ios.less +++ b/apps/documenteditor/mobile/src/less/app-ios.less @@ -38,6 +38,26 @@ } } } + + .navigation-sheet { + border-radius: 10px 10px 0px 0px; + &__title { + display: flex; + justify-content: center; + align-items: center; + background: @background-primary; + padding-bottom: 10px; + p { + font-style: normal; + font-weight: 600; + font-size: 17px; + line-height: 22px; + text-align: center; + color: @text-normal; + margin: 0; + } + } + } } // Color Schemes diff --git a/apps/documenteditor/mobile/src/less/app-material.less b/apps/documenteditor/mobile/src/less/app-material.less index 0182800f7..3b91a75f5 100644 --- a/apps/documenteditor/mobile/src/less/app-material.less +++ b/apps/documenteditor/mobile/src/less/app-material.less @@ -86,6 +86,25 @@ } } } + + .navigation-sheet { + border-radius: 4px 4px 0px 0px; + &__title { + display: flex; + align-items: center; + background: @background-primary; + padding-bottom: 15px; + padding-left: 16px; + p { + font-style: normal; + font-weight: 500; + font-size: 20px; + line-height: 23px; + color: @text-normal; + margin: 0; + } + } + } } // Color Schemes diff --git a/apps/documenteditor/mobile/src/less/app.less b/apps/documenteditor/mobile/src/less/app.less index a4c192194..d6bca0d7e 100644 --- a/apps/documenteditor/mobile/src/less/app.less +++ b/apps/documenteditor/mobile/src/less/app.less @@ -157,4 +157,73 @@ height: 50px; } +// Table of Contents + +.item-contents { + padding: 0 16px; +} + +.style-toc { + &__image { + margin: 0 15px; + max-height: 150px; + overflow: hidden; + &_active { + border: 1.5px solid @brandColor; + } + } +} + +.block > .block-title:first-child, .list > .block-title:first-child { + margin-top: var(--f7-block-margin-vertical); + margin-left: calc(var(--f7-block-padding-horizontal) + var(--f7-safe-area-left)); + margin-right: calc(var(--f7-block-padding-horizontal) + var(--f7-safe-area-right)); +} + +// Navigation + +.empty-screens { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 100%; + &__icon { + margin-bottom: 48px; + } + &__text { + margin: 0 32px; + text-align: center; + } +} + +.swipe-container { + display: flex; + justify-content: center; + height: 40px; + background-color: @background-primary; + .icon-swipe { + margin-top: 8px; + width: 40px; + height: 4px; + background: @background-menu-divider; + border-radius: 2px; + } +} + +.navigation-sheet { + box-shadow: 0px -2px 20px rgba(0, 0, 0, 0.2); + overflow: hidden; + .sheet-modal-inner { + background: @background-tertiary; + } + &.sheet-modal-bottom:before, &.sheet-modal:not(.sheet-modal-top):before { + display: none; + } +} + +.navigation-list { + margin: 0; +} + diff --git a/apps/documenteditor/mobile/src/less/icons-ios.less b/apps/documenteditor/mobile/src/less/icons-ios.less index 58a68b07c..72da50509 100644 --- a/apps/documenteditor/mobile/src/less/icons-ios.less +++ b/apps/documenteditor/mobile/src/less/icons-ios.less @@ -81,6 +81,11 @@ height: 22px; .encoded-svg-mask(''); } + &.icon-navigation { + width: 22px; + height: 22px; + .encoded-svg-mask(''); + } &.icon-feedback { width: 22px; height: 22px; @@ -403,6 +408,11 @@ height: 24px; .encoded-svg-mask('', @toolbar-segment); } + &.icon-table-contents { + width: 24px; + height: 24px; + .encoded-svg-mask(''); + } &.icon-insert-comment { width: 24px; diff --git a/apps/documenteditor/mobile/src/less/icons-material.less b/apps/documenteditor/mobile/src/less/icons-material.less index 9dfdd6d0e..39f800ac5 100644 --- a/apps/documenteditor/mobile/src/less/icons-material.less +++ b/apps/documenteditor/mobile/src/less/icons-material.less @@ -136,6 +136,16 @@ height: 22px; .encoded-svg-mask(''); } + &.icon-table-contents { + width: 24px; + height: 24px; + .encoded-svg-mask(''); + } + &.icon-navigation { + width: 22px; + height: 22px; + .encoded-svg-mask(''); + } &.icon-feedback { width: 22px; height: 22px; diff --git a/apps/documenteditor/mobile/src/page/main.jsx b/apps/documenteditor/mobile/src/page/main.jsx index 0a992a524..6c012beda 100644 --- a/apps/documenteditor/mobile/src/page/main.jsx +++ b/apps/documenteditor/mobile/src/page/main.jsx @@ -12,6 +12,7 @@ import { Device } from '../../../../common/mobile/utils/device' import { Search, SearchSettings } from '../controller/Search'; import ContextMenu from '../controller/ContextMenu'; import { Toolbar } from "../controller/Toolbar"; +import NavigationController from '../controller/settings/Navigation'; class MainPage extends Component { constructor(props) { @@ -21,7 +22,8 @@ class MainPage extends Component { addOptionsVisible: false, addShowOptions: null, settingsVisible: false, - collaborationVisible: false + collaborationVisible: false, + navigationVisible: false }; } @@ -44,6 +46,9 @@ class MainPage extends Component { } else if ( opts === 'coauth' ) { this.state.collaborationVisible && (opened = true); newState.collaborationVisible = true; + } else if( opts === 'navigation') { + this.state.navigationVisible && (opened = true); + newState.navigationVisible = true; } for (let key in this.state) { @@ -75,6 +80,8 @@ class MainPage extends Component { return {settingsVisible: false}; else if ( opts == 'coauth' ) return {collaborationVisible: false}; + else if( opts == 'navigation') + return {navigationVisible: false} }); if ((opts === 'edit' || opts === 'coauth') && Device.phone) { f7.navbar.show('.main-navbar'); @@ -142,24 +149,28 @@ class MainPage extends Component { {/* { Device.phone ? null : } */} - - { - !this.state.editOptionsVisible ? null : - - } - { - !this.state.addOptionsVisible ? null : - - } - { - !this.state.settingsVisible ? null : - - } - { - !this.state.collaborationVisible ? null : - - } - {appOptions.isDocReady && } + + { + !this.state.editOptionsVisible ? null : + + } + { + !this.state.addOptionsVisible ? null : + + } + { + !this.state.settingsVisible ? null : + + } + { + !this.state.collaborationVisible ? null : + + } + { + !this.state.navigationVisible ? null : + + } + {appOptions.isDocReady && } ) } diff --git a/apps/documenteditor/mobile/src/store/mainStore.js b/apps/documenteditor/mobile/src/store/mainStore.js index 1c1d2cc29..4347b4e22 100644 --- a/apps/documenteditor/mobile/src/store/mainStore.js +++ b/apps/documenteditor/mobile/src/store/mainStore.js @@ -34,6 +34,6 @@ export const stores = { storePalette: new storePalette(), storeReview: new storeReview(), storeComments: new storeComments(), - storeToolbarSettings: new storeToolbarSettings() + storeToolbarSettings: new storeToolbarSettings(), }; diff --git a/apps/documenteditor/mobile/src/view/add/Add.jsx b/apps/documenteditor/mobile/src/view/add/Add.jsx index 365bcc566..83da3a2ee 100644 --- a/apps/documenteditor/mobile/src/view/add/Add.jsx +++ b/apps/documenteditor/mobile/src/view/add/Add.jsx @@ -13,6 +13,7 @@ import {AddOtherController} from "../../controller/add/AddOther"; import {PageImageLinkSettings} from "../add/AddImage"; import {PageAddNumber, PageAddBreak, PageAddSectionBreak, PageAddFootnote} from "../add/AddOther"; +import AddTableContentsController from '../../controller/add/AddTableContents'; const routes = [ // Image @@ -41,6 +42,10 @@ const routes = [ path: '/add-footnote/', component: PageAddFootnote, }, + { + path: '/add-table-contents/', + component: AddTableContentsController + } ]; const AddLayoutNavbar = ({ tabs, inPopover }) => { diff --git a/apps/documenteditor/mobile/src/view/add/AddOther.jsx b/apps/documenteditor/mobile/src/view/add/AddOther.jsx index 7823f05e9..1f48b8685 100644 --- a/apps/documenteditor/mobile/src/view/add/AddOther.jsx +++ b/apps/documenteditor/mobile/src/view/add/AddOther.jsx @@ -209,6 +209,9 @@ const AddOther = props => { } + + + {(isShape || isChart) || (isText && disabledAddFootnote) ? null : { + const { t } = useTranslation(); + const _t = t('Add', {returnObjects: true}); + + return ( + + + {_t.textWithPageNumbers} + props.onTableContents(0)}> + {_t.textWithBlueLinks} + props.onTableContents(1)}> + + ) +}; + +export {AddTableContents} diff --git a/apps/documenteditor/mobile/src/view/edit/Edit.jsx b/apps/documenteditor/mobile/src/view/edit/Edit.jsx index 7deceedb2..8cce09c6d 100644 --- a/apps/documenteditor/mobile/src/view/edit/Edit.jsx +++ b/apps/documenteditor/mobile/src/view/edit/Edit.jsx @@ -13,6 +13,7 @@ import EditTableController from "../../controller/edit/EditTable"; import EditChartController from "../../controller/edit/EditChart"; import EditHyperlinkController from "../../controller/edit/EditHyperlink"; import EditHeaderController from "../../controller/edit/EditHeader"; +import EditTableContentsController from "../../controller/edit/EditTableContents"; import {PageTextFonts, PageTextAddFormatting, PageTextBulletsAndNumbers, PageTextLineSpacing, PageTextFontColor, PageTextCustomFontColor, PageTextHighlightColor} from "./EditText"; import {ParagraphAdvSettings, PageParagraphBackColor, PageParagraphCustomColor} from "./EditParagraph"; @@ -20,6 +21,7 @@ import {PageShapeStyleNoFill, PageShapeStyle, PageShapeCustomFillColor, PageShap import {PageImageReorder, PageImageReplace, PageImageWrap, PageLinkSettings} from "./EditImage"; import {PageTableOptions, PageTableWrap, PageTableStyle, PageTableStyleOptions, PageTableCustomFillColor, PageTableBorderColor, PageTableCustomBorderColor} from "./EditTable"; import {PageChartDesign, PageChartDesignType, PageChartDesignStyle, PageChartDesignFill, PageChartDesignBorder, PageChartCustomFillColor, PageChartBorderColor, PageChartCustomBorderColor, PageChartWrap, PageChartReorder} from "./EditChart"; +import { PageEditLeaderTableContents, PageEditStylesTableContents, PageEditStructureTableContents } from './EditTableContents'; const routes = [ //Edit text @@ -183,6 +185,21 @@ const routes = [ { path: '/edit-chart-custom-border-color/', component: PageChartCustomBorderColor, + }, + + // Table Contents + + { + path: '/edit-style-table-contents/', + component: PageEditStylesTableContents + }, + { + path: '/edit-leader-table-contents/', + component: PageEditLeaderTableContents + }, + { + path: '/edit-structure-table-contents/', + component: PageEditStructureTableContents } ]; @@ -242,10 +259,13 @@ const EditLayoutContent = ({ editors }) => { const EditTabs = props => { const { t } = useTranslation(); const _t = t('Edit', {returnObjects: true}); + const api = Common.EditorApi.get(); + const inToc = api.asc_GetTableOfContentsPr(true); const settings = props.storeFocusObjects.settings; const headerType = props.storeFocusObjects.headerType; let editors = []; + if (settings.length < 1) { editors.push({ caption: _t.textSettings, @@ -301,6 +321,13 @@ const EditTabs = props => { component: }) } + if(inToc) { + editors.push({ + caption: _t.textTableOfCont, + id: 'edit-table-contents', + component: + }) + } if (settings.indexOf('hyperlink') > -1) { editors.push({ caption: _t.textHyperlink, diff --git a/apps/documenteditor/mobile/src/view/edit/EditTableContents.jsx b/apps/documenteditor/mobile/src/view/edit/EditTableContents.jsx new file mode 100644 index 000000000..581c882ef --- /dev/null +++ b/apps/documenteditor/mobile/src/view/edit/EditTableContents.jsx @@ -0,0 +1,302 @@ +import React, {Fragment, useEffect, useState } from 'react'; +import {observer, inject} from "mobx-react"; +import {f7, View, List, ListItem, Icon, Row, Button, Page, Navbar, NavRight, Segmented, BlockTitle, Link, ListButton, Toggle, Actions, ActionsButton, ActionsGroup} from 'framework7-react'; +import { useTranslation } from 'react-i18next'; +import {Device} from '../../../../../common/mobile/utils/device'; + +const EditTableContents = props => { + const { t } = useTranslation(); + const _t = t('Edit', {returnObjects: true}); + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + const stylesCount = propsTableContents.get_StylesCount(); + const [type, setType] = useState(0); + const [styleValue, setStyleValue] = useState(propsTableContents.get_StylesType()); + const [pageNumbers, setPageNumbers] = useState(propsTableContents.get_ShowPageNumbers()); + const [rightAlign, setRightAlign] = useState(propsTableContents.get_RightAlignTab()); + const [leaderValue, setLeaderValue] = useState(propsTableContents.get_TabLeader() ? propsTableContents.get_TabLeader() : Asc.c_oAscTabLeader.Dot); + + const arrStyles = (type === 1) ? [ + { displayValue: t('Edit.textCurrent'), value: Asc.c_oAscTOFStylesType.Current }, + { displayValue: t('Edit.textSimple'), value: Asc.c_oAscTOFStylesType.Simple }, + { displayValue: t('Edit.textOnline'), value: Asc.c_oAscTOFStylesType.Web }, + { displayValue: t('Edit.textClassic'), value: Asc.c_oAscTOFStylesType.Classic }, + { displayValue: t('Edit.textDistinctive'), value: Asc.c_oAscTOFStylesType.Distinctive }, + { displayValue: t('Edit.textCentered'), value: Asc.c_oAscTOFStylesType.Centered }, + { displayValue: t('Edit.textFormal'), value: Asc.c_oAscTOFStylesType.Formal } + ] : [ + { displayValue: t('Edit.textSimple'), value: Asc.c_oAscTOCStylesType.Simple }, + { displayValue: t('Edit.textOnline'), value: Asc.c_oAscTOCStylesType.Web }, + { displayValue: t('Edit.textStandard'), value: Asc.c_oAscTOCStylesType.Standard }, + { displayValue: t('Edit.textModern'), value: Asc.c_oAscTOCStylesType.Modern }, + { displayValue: t('Edit.textClassic'), value: Asc.c_oAscTOCStylesType.Classic } + ]; + + const arrLeaders = [ + { value: Asc.c_oAscTabLeader.None, displayValue: t('Edit.textNone') }, + { value: Asc.c_oAscTabLeader.Dot, displayValue: '....................' }, + { value: Asc.c_oAscTabLeader.Hyphen, displayValue: '-----------------' }, + { value: Asc.c_oAscTabLeader.Underscore,displayValue: '__________' } + ]; + + const activeStyle = arrStyles.find(style => style.value === styleValue); + const activeLeader = arrLeaders.find(leader => leader.value === leaderValue); + + const openActionsButtonsRefresh = () => { + f7.actions.create({ + buttons: [ + [ + { + text: t('Edit.textRefreshEntireTable'), + onClick: () => props.onUpdateTableContents('all') + }, + { + text: t('Edit.textRefreshPageNumbersOnly'), + onClick: () => props.onUpdateTableContents('pages') + } + ], + [ + { + text: t('Edit.textCancel'), + bold: true + } + ] + ] + }).open(); + } + + return ( + + + + + + + {t('Edit.textPageNumbers')} + { + setPageNumbers(!pageNumbers); + props.onPageNumbers(!pageNumbers); + }}> + + {pageNumbers && + + {t('Edit.textRightAlign')} + { + setRightAlign(!rightAlign); + props.onRightAlign(!rightAlign); + }}> + + } + {(pageNumbers && rightAlign) && + + } + + + + openActionsButtonsRefresh()} /> + props.onRemoveTableContents()} /> + + + ) +}; + +const PageEditStylesTableContents = props => { + const { t } = useTranslation(); + const _t = t('Edit', {returnObjects: true}); + const [styleValue, setStyleValue] = useState(props.styleValue); + const widthImage = !Device.phone ? '330px' : window.innerWidth - 30 + 'px'; + + useEffect(() => { + props.getStylesImages(); + }, []); + + return ( + + + {Device.phone && + + + + + + } + + + {props.arrStyles.map((style, index) => { + return ( + { + setStyleValue(style.value); + props.setStyleValue(style.value); + props.onStyle(style.value) + }}> + {style.displayValue} + + + + + ) + })} + + + ) +} + +const PageEditLeaderTableContents = props => { + const { t } = useTranslation(); + const _t = t('Edit', {returnObjects: true}); + const [leaderValue, setLeaderValue] = useState(props.leaderValue); + + return ( + + + {Device.phone && + + + + + + } + + + {props.arrLeaders.map((leader, index) => { + return ( + { + setLeaderValue(leader.value); + props.setLeaderValue(leader.value); + props.onLeader(leader.value); + }}> + ) + })} + + + ) +} + +const PageEditStructureTableContents = props => { + const { t } = useTranslation(); + const _t = t('Edit', {returnObjects: true}); + const isAndroid = Device.android; + const api = Common.EditorApi.get(); + const propsTableContents = api.asc_GetTableOfContentsPr(); + const {styles, end, count} = props.fillTOCProps(propsTableContents); + const chosenStyles = styles.filter(style => style.checked); + + const [structure, setStructure] = useState(count ? 1 : 0); + const [amountLevels, setAmountLevels] = useState(end); + + const addNewStyle = (style) => { + let indexStyle = chosenStyles.findIndex(currentStyle => currentStyle.name === style.name); + + if(indexStyle === -1) { + chosenStyles.push(style); + } + } + + return ( + + + {Device.phone && + + + + + + } + + + setStructure(0)}> + setStructure(1)}> + + {structure === 0 ? + + + {!isAndroid && {amountLevels === -1 ? '-' : amountLevels}} + + + { + if(amountLevels > 1) { + setAmountLevels(amountLevels - 1); + props.onLevelsChange(amountLevels - 1); + } + }}> + {isAndroid ? : ' - '} + + {isAndroid && {amountLevels === -1 ? '-' : amountLevels}} + { + if(amountLevels < 9) { + if(amountLevels === -1) { + setAmountLevels(9); + props.onLevelsChange(9); + } else { + setAmountLevels(amountLevels + 1); + props.onLevelsChange(amountLevels + 1); + } + } + }}> + {isAndroid ? : ' + '} + + + + + + : + + {styles.map((style, index) => { + return ( + + {!isAndroid && {style.value}} + + + { + if(style.value > 1) { + setAmountLevels(-1); + addNewStyle(style); + props.addStyles(chosenStyles, style.name, style.value - 1); + } + }}> + {isAndroid ? : ' - '} + + {isAndroid && {style.value}} + { + if(style.value < 9) { + setAmountLevels(-1); + addNewStyle(style); + props.addStyles(chosenStyles, style.name, style.value + 1); + } + }}> + {isAndroid ? : ' + '} + + + + + ) + })} + + } + + ) +} + +export { + EditTableContents, + PageEditStylesTableContents, + PageEditLeaderTableContents, + PageEditStructureTableContents +}; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/view/settings/Navigation.jsx b/apps/documenteditor/mobile/src/view/settings/Navigation.jsx new file mode 100644 index 000000000..87d626c9c --- /dev/null +++ b/apps/documenteditor/mobile/src/view/settings/Navigation.jsx @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from "react"; +import { Device } from '../../../../../common/mobile/utils/device'; +import {f7, View, List, ListItem, Icon, Row, Button, Page, Navbar, NavRight, Segmented, BlockTitle, Link, ListButton, Toggle, Actions, ActionsButton, ActionsGroup, Sheet} from 'framework7-react'; +import { useTranslation } from 'react-i18next'; + +const NavigationPopover = props => { + const { t } = useTranslation(); + const _t = t('Settings', {returnObjects: true}); + const api = Common.EditorApi.get(); + const navigationObject = api.asc_ShowDocumentOutline(); + const [currentPosition, setCurrentPosition] = useState(navigationObject.get_CurrentPosition()); + const arrHeaders = props.updateNavigation(); + + return ( + + + {!arrHeaders || !arrHeaders.length + ? + + {t('Settings.textEmptyScreens')} + + : + + {arrHeaders.map((header, index) => { + return ( + { + setCurrentPosition(header.index); + props.onSelectItem(header.index); + }}> + ) + })} + + } + + ) +} + +const NavigationSheet = props => { + const { t } = useTranslation(); + const api = Common.EditorApi.get(); + const navigationObject = api.asc_ShowDocumentOutline(); + const [currentPosition, setCurrentPosition] = useState(navigationObject.get_CurrentPosition()); + const arrHeaders = props.updateNavigation(); + + const [stateHeight, setHeight] = useState('45%'); + const [stateOpacity, setOpacity] = useState(1); + + const [stateStartY, setStartY] = useState(); + const [isNeedClose, setNeedClose] = useState(false); + + const handleTouchStart = (event) => { + const touchObj = event.changedTouches[0]; + setStartY(parseInt(touchObj.clientY)); + }; + + const handleTouchMove = (event) => { + const touchObj = event.changedTouches[0]; + const dist = parseInt(touchObj.clientY) - stateStartY; + + if (dist < 0) { + setHeight('90%'); + setOpacity(1); + setNeedClose(false); + } else if (dist < 80) { + setHeight('45%'); + setOpacity(1); + setNeedClose(false); + } else { + setNeedClose(true); + setOpacity(0.6); + } + }; + + const handleTouchEnd = (event) => { + const touchObj = event.changedTouches[0]; + const swipeEnd = parseInt(touchObj.clientY); + const dist = swipeEnd - stateStartY; + + if (isNeedClose) { + f7.sheet.close('#view-navigation-sheet'); + } else if (stateHeight === '90%' && dist > 20) { + setHeight('45%'); + } + }; + + useEffect(() => { + f7.sheet.open('#view-navigation-sheet', true); + }, []); + + return ( + props.onclosed()} style={{height: `${stateHeight}`, opacity: `${stateOpacity}`}}> + + + + + {t('Settings.textNavigation')} + + {!arrHeaders || !arrHeaders.length + ? + + {t('Settings.textEmptyScreens')} + + : + + {arrHeaders.map((header, index) => { + return ( + { + setCurrentPosition(header.index); + props.onSelectItem(header.index); + }}> + ) + })} + + } + + ) +} + +export { + NavigationPopover, + NavigationSheet +}; \ No newline at end of file diff --git a/apps/documenteditor/mobile/src/view/settings/Settings.jsx b/apps/documenteditor/mobile/src/view/settings/Settings.jsx index 662f78f11..299c5ad0a 100644 --- a/apps/documenteditor/mobile/src/view/settings/Settings.jsx +++ b/apps/documenteditor/mobile/src/view/settings/Settings.jsx @@ -12,6 +12,7 @@ import ApplicationSettingsController from "../../controller/settings/Application import { DocumentFormats, DocumentMargins, DocumentColorSchemes } from "./DocumentSettings"; import { MacrosSettings } from "./ApplicationSettings"; import About from '../../../../../common/mobile/lib/view/About'; +import NavigationController from '../../controller/settings/Navigation'; const routes = [ { @@ -53,6 +54,13 @@ const routes = [ { path: '/about/', component: About + }, + + // Navigation + + { + path: '/navigation/', + component: NavigationController } ]; @@ -79,9 +87,14 @@ const SettingsList = inject("storeAppOptions", "storeReview")(observer(props => } }; - const onOpenCollaboration = async () => { - await closeModal(); - await props.openOptions('coauth'); + const onOpenCollaboration = () => { + closeModal(); + props.openOptions('coauth'); + } + + const onOpenNavigation = () => { + closeModal(); + props.openOptions('navigation'); } // set mode @@ -121,6 +134,14 @@ const SettingsList = inject("storeAppOptions", "storeReview")(observer(props => } + { + if(Device.phone) { + onOpenNavigation(); + } else { + onoptionclick.bind(this, "/navigation/")(); + }}}> + + {window.matchMedia("(max-width: 359px)").matches ?
{t('Settings.textEmptyScreens')}
{t('Settings.textNavigation')}