From 1253b81a0138671a573751d3d754eaa90e2536b1 Mon Sep 17 00:00:00 2001 From: Nikolay Lopin Date: Wed, 21 Feb 2018 03:08:40 +0300 Subject: [PATCH 1/6] Introduce tabs with minimal width --- browser/components/SnippetTab.styl | 4 +- browser/main/Detail/SnippetNoteDetail.js | 98 ++++++++++++++++++++-- browser/main/Detail/SnippetNoteDetail.styl | 12 ++- 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/browser/components/SnippetTab.styl b/browser/components/SnippetTab.styl index da7cff0f..a839999a 100644 --- a/browser/components/SnippetTab.styl +++ b/browser/components/SnippetTab.styl @@ -1,6 +1,7 @@ .root position relative flex 1 + min-width 70px overflow hidden &:hover .deleteButton @@ -21,7 +22,8 @@ height 29px overflow ellipsis text-align left - padding-right 30px + padding-right 23px + margin-right -23px border none background-color transparent transition 0.15s diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 366c785b..c6f7179d 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -8,7 +8,7 @@ import StarButton from './StarButton' import TagSelect from './TagSelect' import FolderSelect from './FolderSelect' import dataApi from 'browser/main/lib/dataApi' -import { hashHistory } from 'react-router' +import {hashHistory} from 'react-router' import ee from 'browser/main/lib/eventEmitter' import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' @@ -17,14 +17,14 @@ import StatusBar from '../StatusBar' import context from 'browser/lib/context' import ConfigManager from 'browser/main/lib/ConfigManager' import _ from 'lodash' -import { findNoteTitle } from 'browser/lib/findNoteTitle' +import {findNoteTitle} from 'browser/lib/findNoteTitle' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import TrashButton from './TrashButton' import PermanentDeleteButton from './PermanentDeleteButton' import InfoButton from './InfoButton' import InfoPanel from './InfoPanel' import InfoPanelTrashed from './InfoPanelTrashed' -import { formatDate } from 'browser/lib/date-formatter' +import {formatDate} from 'browser/lib/date-formatter' function pass (name) { switch (name) { @@ -52,6 +52,9 @@ class SnippetNoteDetail extends React.Component { this.state = { isMovingNote: false, snippetIndex: 0, + showArrows: false, + enableLeftArrow: false, + enableRightArrow: false, note: Object.assign({ description: '' }, props.note, { @@ -60,6 +63,19 @@ class SnippetNoteDetail extends React.Component { } } + componentDidMount () { + const visibleTabs = this.visibleTabs + const allTabs = this.allTabs + + if (visibleTabs.offsetWidth < allTabs.offsetWidth) { + this.setState({ + showArrows: visibleTabs.offsetWidth < allTabs.offsetWidth, + enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.offsetWidth, + enableLeftArrow: allTabs.offsetLeft !== 0 + }) + } + } + componentWillReceiveProps (nextProps) { if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { if (this.saveQueue != null) this.saveNow() @@ -227,6 +243,47 @@ class SnippetNoteDetail extends React.Component { ee.emit('editor:fullscreen') } + handleTabMoveLeftButtonClick (e) { + { + const left = Math.abs(this.allTabs.offsetLeft) + + const tabs = this.allTabs.querySelectorAll('div') + const lastVisibleTab = Array.from(tabs).find((tab) => { + return tab.offsetLeft + tab.offsetWidth >= left + }) + + const visiblePart = lastVisibleTab.offsetWidth - left + lastVisibleTab.offsetLeft + const showTab = (visiblePart > lastVisibleTab.offsetWidth * 0.75) ? lastVisibleTab.previousSibling : lastVisibleTab + + let newLeft = showTab.offsetLeft + if (showTab.previousSibling) { + newLeft -= showTab.previousSibling.offsetWidth * 0.6 + } + + this.moveTabBarBy(-newLeft) + } + } + + handleTabMoveRightButtonClick (e) { + const left = this.allTabs.offsetLeft + const width = this.visibleTabs.offsetWidth + + const tabs = this.allTabs.querySelectorAll('div') + const lastVisibleTab = Array.from(tabs).find((tab) => { + return tab.offsetLeft + tab.offsetWidth >= width - left + }) + + if (lastVisibleTab) { + let newLeft = lastVisibleTab.offsetLeft + lastVisibleTab.offsetWidth - width + + if (width - left - lastVisibleTab.offsetLeft > lastVisibleTab.offsetWidth * 0.75) { + newLeft += lastVisibleTab.nextSibling.offsetWidth + } + + this.moveTabBarBy(-newLeft) + } + } + handleTabPlusButtonClick (e) { this.addSnippet() } @@ -456,6 +513,17 @@ class SnippetNoteDetail extends React.Component { this.refs.description.focus() } + moveTabBarBy (x) { + this.allTabs.addEventListener('transitionend', () => { + this.setState({ + enableRightArrow: this.allTabs.offsetLeft !== this.visibleTabs.offsetWidth - this.allTabs.offsetWidth, + enableLeftArrow: this.allTabs.offsetLeft !== 0 + }) + }, {once: true}) + + this.allTabs.style.left = `${x}px` + } + addSnippet () { const { note } = this.state @@ -470,6 +538,8 @@ class SnippetNoteDetail extends React.Component { note, snippetIndex }, () => { + const newLeft = this.visibleTabs.offsetWidth - this.allTabs.offsetWidth + this.moveTabBarBy(newLeft) this.refs['tab-' + snippetIndex].startRenaming() }) } @@ -680,10 +750,26 @@ class SnippetNoteDetail extends React.Component { />
-
- {tabList} + +
{ this.visibleTabs = tabs }}> +
{ this.allTabs = tabs }}> + {tabList} +
- + -
{ this.visibleTabs = tabs }}> +
{ this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
{ this.allTabs = tabs }}> {tabList}
diff --git a/browser/main/Detail/SnippetNoteDetail.styl b/browser/main/Detail/SnippetNoteDetail.styl index 752fce27..789d5186 100644 --- a/browser/main/Detail/SnippetNoteDetail.styl +++ b/browser/main/Detail/SnippetNoteDetail.styl @@ -49,7 +49,7 @@ .allTabs display flex - position absolute + position relative overflow visible left 0 transition left 0.1s From a222fd07862e0a05ce94d0c7205005ea168e6881 Mon Sep 17 00:00:00 2001 From: Nikolay Lopin Date: Thu, 8 Mar 2018 01:02:49 +0300 Subject: [PATCH 6/6] Add animation for scrolling with buttons --- browser/main/Detail/SnippetNoteDetail.js | 51 +++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 516f9179..34f2a13c 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -62,6 +62,8 @@ class SnippetNoteDetail extends React.Component { snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet)) }) } + + this.scrollToNextTabThreshold = 0.7 } componentDidMount () { @@ -256,14 +258,14 @@ class SnippetNoteDetail extends React.Component { if (lastVisibleTab) { const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left - let scrollToTab = lastVisibleTab - if (visiblePart > lastVisibleTab.offsetWidth * 0.75 && lastVisibleTab.previousSibling) { - scrollToTab = lastVisibleTab.previousSibling - } + const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold + const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling) + ? lastVisibleTab.previousSibling + : lastVisibleTab - // FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61) + // FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll) this.moveToTab(scrollToTab) - // scrollToTab.scrollIntoView({block: 'end'}) + // scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'start', block: 'start'}) } } } @@ -278,16 +280,15 @@ class SnippetNoteDetail extends React.Component { }) if (lastVisibleTab) { - let scrollToTab = lastVisibleTab const visiblePart = width + left - lastVisibleTab.offsetLeft + const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold + const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling) + ? lastVisibleTab.nextSibling + : lastVisibleTab - if (visiblePart > lastVisibleTab.offsetWidth * 0.75 && lastVisibleTab.nextSibling) { - scrollToTab = lastVisibleTab.nextSibling - } - - // FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61) - // scrollToTab.scrollIntoView({inline: 'end', block: 'end'}) + // FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll) this.moveToTab(scrollToTab) + // scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'end', block: 'end'}) } } @@ -537,16 +538,28 @@ class SnippetNoteDetail extends React.Component { moveToTab (tab) { const easeOutCubic = t => (--t) * t * t + 1 - const targetScrollChange = 50 // TODO count target value - const animationTiming = 300 const startScrollPosition = this.visibleTabs.scrollLeft + const animationTiming = 300 + const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab + + let scrollBy = (tab.offsetLeft - startScrollPosition) + + if (tab.offsetLeft > startScrollPosition) { + // if tab is on the right side and we want to show the whole tab in visible area, + // we need to include width of the tab and visible area in the formula + // ___________________________________________ + // |____|_______|________|________|_show_this_| + // ↑_____________________↑ + // visible area + scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth) + } let startTime = null const scrollAnimation = time => { startTime = startTime || time const elapsed = (time - startTime) / animationTiming - this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * targetScrollChange + this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff if (elapsed < 1) { window.requestAnimationFrame(scrollAnimation) } else { @@ -583,8 +596,10 @@ class SnippetNoteDetail extends React.Component { snippetIndex }, this.getArrowsState()), () => { if (this.state.showArrows) { - const newLeft = this.visibleTabs.offsetWidth - this.allTabs.scrollWidth - this.moveTabBarBy(newLeft) + const tabs = this.allTabs.querySelectorAll('div') + if (tabs) { + this.moveToTab(tabs[snippetIndex]) + } } this.refs['tab-' + snippetIndex].startRenaming() })