diff --git a/services/web/app/coffee/infrastructure/Features.coffee b/services/web/app/coffee/infrastructure/Features.coffee index 6e39ee6370..5dfe73f05f 100644 --- a/services/web/app/coffee/infrastructure/Features.coffee +++ b/services/web/app/coffee/infrastructure/Features.coffee @@ -16,5 +16,7 @@ module.exports = Features = return Settings.accountMerge? and Settings.overleaf? when 'rich-text' return Settings.showRichText + when 'custom-togglers' + return Settings.overleaf? else throw new Error("unknown feature: #{feature}") diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index f4287334c8..214ae202fe 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -62,6 +62,9 @@ block content ng-hide="state.loading", resize-on="layout:chat:resize", minimum-restore-size-west="130" + custom-toggler-pane=hasFeature('custom-togglers') ? "'west'" : "false" + custom-toggler-msg-when-open=hasFeature('custom-togglers') ? "'" + translate("tooltip_hide_filetree") + "'" : "false" + custom-toggler-msg-when-closed=hasFeature('custom-togglers') ? "'" + translate("tooltip_show_filetree") + "'" : "false" ) .ui-layout-west include ./editor/file-tree diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index b432a7aa09..2b42cda965 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -8,6 +8,9 @@ div.full-size( initial-size-east="'50%'" minimum-restore-size-east="300" allow-overflow-on="'center'" + custom-toggler-pane=hasFeature('custom-togglers') ? "'east'" : "false" + custom-toggler-msg-when-open=hasFeature('custom-togglers') ? "'" + translate("tooltip_hide_pdf") + "'" : "false" + custom-toggler-msg-when-closed=hasFeature('custom-togglers') ? "'" + translate("tooltip_show_pdf") + "'" : "false" ) .ui-layout-center( ng-controller="ReviewPanelController", @@ -92,7 +95,6 @@ div.full-size( ng-click="syncToCode()" ) i.synctex-control-icon - div.full-size( ng-if="ui.pdfLayout == 'flat'" ng-show="ui.view == 'pdf'" diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee index fcc75b29a1..3c8f2900f1 100644 --- a/services/web/public/coffee/ide/directives/layout.coffee +++ b/services/web/public/coffee/ide/directives/layout.coffee @@ -2,12 +2,17 @@ define [ "base" "libs/jquery-layout" ], (App) -> - App.directive "layout", ["$parse", "ide", ($parse, ide) -> + App.directive "layout", ["$parse", "$compile", "ide", ($parse, $compile, ide) -> return { compile: () -> pre: (scope, element, attrs) -> name = attrs.layout + customTogglerPane = scope.$eval(attrs.customTogglerPane or "false") + customTogglerMsgWhenOpen = scope.$eval(attrs.customTogglerMsgWhenOpen or "false") + customTogglerMsgWhenClosed = scope.$eval(attrs.customTogglerMsgWhenClosed or "false") + hasCustomToggler = customTogglerPane != false and customTogglerMsgWhenOpen != false and customTogglerMsgWhenClosed != false + if attrs.spacingOpen? spacingOpen = parseInt(attrs.spacingOpen, 10) else @@ -23,6 +28,10 @@ define [ spacing_closed: spacingClosed slidable: false enableCursorHotkey: false + onopen: (pane) => + onPaneOpen(pane) + onclose: (pane) => + onPaneClose(pane) onresize: () => onInternalResize() maskIframesOnResize: scope.$eval( @@ -62,6 +71,15 @@ define [ right: state.east.size }) + repositionCustomToggler = () -> + if !customTogglerEl? + return + state = element.layout().readState() + positionAnchor = if customTogglerPane == "east" then "right" else "left" + paneState = state[customTogglerPane] + if paneState? + customTogglerEl.css(positionAnchor, if paneState.initClosed then 0 else paneState.size) + resetOpenStates = () -> state = element.layout().readState() if attrs.openEast? and state.east? @@ -73,6 +91,8 @@ define [ state = element.layout().readState() scope.$broadcast "layout:#{name}:resize", state repositionControls() + if hasCustomToggler + repositionCustomToggler() resetOpenStates() oldWidth = element.width() @@ -94,6 +114,44 @@ define [ if attrs.resizeOn? scope.$on attrs.resizeOn, () -> onExternalResize() + if hasCustomToggler + state = element.layout().readState() + customTogglerScope = scope.$new() + + customTogglerScope.isOpen = true + + if state[customTogglerPane]?.initClosed == true + customTogglerScope.isOpen = false + + customTogglerScope.tooltipMsgWhenOpen = customTogglerMsgWhenOpen + customTogglerScope.tooltipMsgWhenClosed = customTogglerMsgWhenClosed + + customTogglerScope.tooltipPlacement = if customTogglerPane == "east" then "left" else "right" + customTogglerScope.handleClick = () -> + element.layout().toggle(customTogglerPane) + repositionCustomToggler() + customTogglerEl = $compile(" + + ")(customTogglerScope) + element.append(customTogglerEl) + + onPaneOpen = (pane) -> + if !hasCustomToggler and pane != customTogglerPane + return + customTogglerEl.scope().$applyAsync () -> + customTogglerEl.scope().isOpen = true + + onPaneClose = (pane) -> + if !hasCustomToggler and pane != customTogglerPane + return + customTogglerEl.scope().$applyAsync () -> + customTogglerEl.scope().isOpen = false + # Save state when exiting $(window).unload () -> ide.localStorage("layout.#{name}", element.layout().readState()) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index fa39552c27..d53cd1dba7 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -14,7 +14,7 @@ @import "./editor/review-panel.less"; @ui-layout-toggler-def-height: 50px; -@ui-resizer-extra-hit-area: 8px; +@ui-resizer-size: 7px; @keyframes blink { 0% { @@ -107,7 +107,7 @@ height: 0; background: @editor-loading-logo-background-url no-repeat bottom / 100%; - &::after { + &::after { content: ''; position: absolute; height: inherit; @@ -116,7 +116,7 @@ left: 0; background: @editor-loading-logo-foreground-url no-repeat bottom / 100%; transition: height .5s; - } + } } .loading-screen-label { margin: 0; @@ -302,40 +302,32 @@ } .ui-layout-resizer when (@is-overleaf = true) { - margin-left: -(@ui-resizer-extra-hit-area) !important; - margin-right: -(@ui-resizer-extra-hit-area - 1px) !important; - padding-left: @ui-resizer-extra-hit-area !important; - padding-right: @ui-resizer-extra-hit-area !important; z-index: 5 !important; - box-sizing: content-box; - background-image: linear-gradient(90deg, - transparent, - transparent (@ui-resizer-extra-hit-area - 1px), - @editor-resizer-bg-color (@ui-resizer-extra-hit-area - 1px), - @editor-resizer-bg-color (@ui-resizer-extra-hit-area + 1px), - transparent (@ui-resizer-extra-hit-area + 1px), - transparent); - - .ui-layout-toggler { - padding: 0 @ui-resizer-extra-hit-area !important; - background-image: linear-gradient(90deg, - transparent, - transparent (@ui-resizer-extra-hit-area - 1px), - @editor-toggler-bg-color (@ui-resizer-extra-hit-area - 1px), - @editor-toggler-bg-color (@ui-resizer-extra-hit-area + 1px), - transparent (@ui-resizer-extra-hit-area + 1px), - transparent); - - &:hover { - background-image: linear-gradient(90deg, - transparent, - transparent (@ui-resizer-extra-hit-area - 2px), - @editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area - 2px), - @editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area + 2px), - transparent (@ui-resizer-extra-hit-area + 2px), - transparent); + width: @ui-resizer-size !important; + background-color: @editor-resizer-bg-color; + &.ui-layout-resizer-closed { + &::before, + &::after { + content: none; } } + + &::before, + &::after { + content: '\2847'; + display: block; + position: absolute; + text-align: center; + left: -2px; + -webkit-font-smoothing: antialiased; + width: 100%; + font-size: 24px; + top: 25%; + color: @ol-blue-gray-2; + } + &::after { + top: 75%; + } } .ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed { @@ -353,54 +345,66 @@ } } -.ui-layout-toggler.ui-layout-toggler-closed when (@is-overleaf = true) { - background-color: @editor-resizer-bg-color; - background-image: none; - line-height: @ui-layout-toggler-def-height; - - &::before { - content: "\22EE"; // Vertical ellipsis - display: block; - color: #FFF; - font-weight: 700; - font-size: @font-size-h2; - width: @ui-resizer-extra-hit-area / 2; +.ui-layout-toggler when (@is-overleaf = true) { + display: none !important; +} + +.custom-toggler when (@is-overleaf = true) { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + width: @ui-resizer-size !important;; + height: 50px; + margin-top: -25px; + top: 50%; + z-index: 6; + background-color: @editor-toggler-bg-color; + + &:hover, + &:focus { + outline: none; + text-decoration: none; } + // Increase hit area + &::before { + content: ''; + display: block; + position: absolute; + top: 0; + right: -3px; + bottom: 0; + left: -3px; + } + + &::after { + font-family: FontAwesome; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-size: 65%; + font-weight: bold; + color: #FFF; + user-select: none; + pointer-events: none; + } + &:hover { background-color: @editor-toggler-hover-bg-color; - background-image: none; - } - .ui-layout-resizer-west > & { - border-radius: 0 @border-radius-base @border-radius-base 0; - &::before { - margin-left: -2px; - } - } - .ui-layout-resizer-east > & { - border-radius: @border-radius-base 0 0 @border-radius-base; - &::before { - margin-left: (-1 - @ui-resizer-extra-hit-area); - } } } + .custom-toggler-east::after { + content: '\f105'; + } + .custom-toggler-west::after { + content: '\f104'; + } -.ui-layout-toggler-east when (@is-overleaf = true) { - &.ui-layout-toggler-open { - cursor: e-resize !important + .custom-toggler-closed.custom-toggler-east::after { + content: '\f104'; } - &.ui-layout-toggler-closed { - cursor: w-resize !important + .custom-toggler-closed.custom-toggler-west::after { + content: '\f105'; } -} - -.ui-layout-toggler-west when (@is-overleaf = true) { - &.ui-layout-toggler-open { - cursor: w-resize !important - } - &.ui-layout-toggler-closed { - cursor: e-resize !important - } -} .ui-layout-resizer-dragging { background-color: @editor-resizer-bg-color-dragging; @@ -624,4 +628,3 @@ height: auto; border-bottom: 1px solid @modal-header-border-color; } - diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 0f76816fd5..af597933ea 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -270,7 +270,7 @@ } .synctex-controls when (@is-overleaf = true) { - margin-right: -11px; + margin-right: -8px; } .synctex-control { display: block; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 6ffceaf176..45dd0232dd 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -227,9 +227,9 @@ @file-tree-droppable-bg-color : tint(@ol-green, 5%); // Editor resizers -@editor-resizer-bg-color : @ol-blue-gray-6; -@editor-resizer-bg-color-dragging : transparent; -@editor-toggler-bg-color : @ol-blue-gray-2; +@editor-resizer-bg-color : @ol-blue-gray-5; +@editor-resizer-bg-color-dragging : @ol-blue-gray-5; +@editor-toggler-bg-color : darken(@ol-blue-gray-2, 15%); @editor-toggler-hover-bg-color : @ol-green; @synctex-controls-z-index : 6; @synctex-controls-padding : 0;