add tab scrolling and drag&drop (#953)

* add tab scrolling and drag&drop

* fix tab maximal width without side bar

* use dragula instead of vue-draggable

* Update changelog

* fix issues with maximal side bar width

If the side bar is resized more than 50vw then issues occur because the
commited width is not limited to 50vw or if the window is resized.

* reordered tabs after dropping and some improvements
This commit is contained in:
Felix Häusler 2019-04-28 20:02:05 +02:00 committed by GitHub
parent 3b9c16779d
commit 620df2ee69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 555 additions and 196 deletions

View File

@ -5,6 +5,8 @@
**:cactus:Feature**
- The cursor jump to the end of format or to the next brackets when press `tab`(#976)
- Tab drag & drop inside the window
- Scrollable tabs
**:butterfly:Optimization**

View File

@ -170,7 +170,9 @@
"codemirror": "^5.46.0",
"command-exists": "^1.2.8",
"dayjs": "^1.8.13",
"dom-autoscroller": "^2.3.4",
"dompurify": "^1.0.10",
"dragula": "^3.7.2",
"electron-is-accelerator": "^0.1.2",
"element-resize-detector": "^1.2.0",
"element-ui": "^2.8.2",

View File

@ -152,6 +152,9 @@
// prevent Chromium's default behavior and try to open the first file
window.addEventListener('dragover', e => {
// Cancel to allow tab drag&drop.
if (!e.dataTransfer.types.length) return
e.preventDefault()
if (e.dataTransfer.types.indexOf('Files') >= 0) {
if (e.dataTransfer.items.length === 1 && /png|jpg|jpeg|gif/.test(e.dataTransfer.items[0].type)) {
@ -201,6 +204,7 @@
}
.editor-middle {
display: flex;
flex-direction: column;
flex: 1;
min-height: 100vh;
position: relative;

View File

@ -48,9 +48,27 @@
}
.editor-tabs {
border-bottom: 1px solid #1d1d1d;
box-shadow: none !important;
}
.editor-tabs:after {
position: absolute;
content: '';
border-bottom: 1px solid #1d1d1d;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
.editor-tabs ul.tabs-container:after {
position: absolute;
content: '';
border-bottom: 1px solid #1d1d1d;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
.tabs-container > li,
.tabs-container > li.active {
background: var(--editorBgColor) !important;

View File

@ -27,15 +27,32 @@
--editorAreaWidth: 700px;
}
.title-bar.tabs-visible {
.editor-tabs {
background: #f3f3f3 !important;
box-shadow: none !important;
}
.editor-tabs:after {
position: absolute;
content: '';
border-bottom: 1px solid #dddddd;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
.editor-tabs ul.tabs-container:after {
position: absolute;
content: '';
border-bottom: 1px solid #dddddd;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
.title-bar-editor-bg.tabs-visible {
background: #f3f3f3 !important;
}
.editor-tabs {
background: #f3f3f3 !important;
border-bottom: 1px solid #dddddd !important;
box-shadow: none !important;
}
.tabs-container > li {
background: none !important;
}
@ -45,7 +62,7 @@
border-bottom: none;
background: var(--floatBgColor) !important;
}
.tabs-container > li.active:not(:last-child):after {
.tabs-container > li.active:after {
top: 0 !important;
bottom: auto !important;
background: var(--themeColor) !important;

View File

@ -110,9 +110,27 @@
}
.editor-tabs {
border-bottom: 1px solid #181a1f;
box-shadow: none !important;
}
.editor-tabs:after {
position: absolute;
content: '';
border-bottom: 1px solid #181a1f;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
.editor-tabs ul.tabs-container:after {
position: absolute;
content: '';
border-bottom: 1px solid #181a1f;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
.tabs-container > li,
.tabs-container > li.active {
background: var(--editorBgColor) !important;
@ -121,7 +139,7 @@
border: 1px solid #181a1f;
border-bottom: none;
}
.tabs-container > li.active:not(:last-child):after {
.tabs-container > li.active:after {
top: 0 !important;
right: auto !important;
width: 2px !important;

View File

@ -31,7 +31,7 @@
box-shadow: none !important;
}
.tabs-container > li:not(:last-child) {
.tabs-container > li {
border-right: 1px solid #e5e5e5 !important;
background: var(--editorBgColor) !important;
}

View File

@ -68,9 +68,8 @@
<style scoped>
.editor-with-tabs {
width: 100%;
position: relative;
height: 100%;
padding-top: var(--titleBarHeight);
flex: 1;
display: flex;
flex-direction: column;

View File

@ -1,13 +1,22 @@
<template>
<div
class="editor-tabs"
:style="{'max-width': showSideBar ? `calc(100vw - ${sideBarWidth}px` : '100vw' }"
>
<div
class="editor-tabs"
class="scrollable-tabs"
ref="tabContainer"
>
<ul class="tabs-container">
<ul
ref="tabDropContainer"
class="tabs-container"
>
<li
:title="file.pathname"
:class="{'active': currentFile.id === file.id, 'unsaved': !file.isSaved }"
v-for="(file, index) of tabs"
:key="index"
v-for="file of tabs"
:key="file.id"
:data-id="file.id"
@click.stop="selectFile(file)"
>
<span>{{ file.filename }}</span>
@ -18,32 +27,112 @@
<use id="default-close-icon" xlink:href="#icon-close-small"></use>
</svg>
</li>
<li class="new-file">
<svg class="icon" aria-hidden="true"
@click.stop="newFile()"
>
<use xlink:href="#icon-plus"></use>
</svg>
</li>
</ul>
</div>
<div
class="new-file"
>
<svg class="icon" aria-hidden="true"
@click.stop="newFile()"
>
<use xlink:href="#icon-plus"></use>
</svg>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import autoScroll from 'dom-autoscroller'
import dragula from 'dragula'
import { tabsMixins } from '../../mixins'
export default {
data () {
this.autoScroller = null
this.drake = null
return {}
},
mixins: [tabsMixins],
computed: {
...mapState({
currentFile: state => state.editor.currentFile,
tabs: state => state.editor.tabs
'currentFile': state => state.editor.currentFile,
'tabs': state => state.editor.tabs,
'showSideBar': state => state.layout.showSideBar,
'sideBarWidth': state => state.layout.sideBarWidth
})
},
methods: {
newFile () {
this.$store.dispatch('NEW_BLANK_FILE')
},
handleTabScroll (event) {
// Use mouse wheel value first but prioritize X value more (e.g. touchpad input).
let delta = event.deltaY
if (event.deltaX !== 0) {
delta = event.deltaX
}
const tabs = this.$refs.tabContainer
const newLeft = Math.max(0, Math.min(tabs.scrollLeft + delta, tabs.scrollWidth ))
tabs.scrollLeft = newLeft
}
},
mounted () {
this.$nextTick(() => {
const tabs = this.$refs.tabContainer
// Allow to scroll through the tabs by mouse wheel or touchpad.
tabs.addEventListener('wheel', this.handleTabScroll)
// Allow tab drag and drop to reorder tabs.
const drake = this.drake = dragula([ this.$refs.tabDropContainer ], {
direction: 'horizontal',
revertOnSpill: true,
mirrorContainer: this.$refs.tabDropContainer,
ignoreInputTextSelection: false
}).on('drop', (el, target, source, sibling) => {
// Current tab that was dropped and need to be reordered.
const droppedId = el.getAttribute('data-id')
// This should be the next tab (tab | ... | el | sibling | tab | ...) but may be
// the mirror image or null (tab | ... | el | sibling or null) if last tab.
const nextTabId = sibling && sibling.getAttribute('data-id')
const isLastTab = !sibling || sibling.classList.contains('gu-mirror')
if (!droppedId || (sibling && !nextTabId)) {
throw new Error('Cannot reorder tabs: invalid tab id.')
}
this.$store.dispatch('EXCHANGE_TABS_BY_ID', {
fromId: droppedId,
toId: isLastTab ? null : nextTabId
})
})
// TODO(perf): Create a copy of dom-autoscroller and just hook tabs-container to
// improve performance. Currently autoScroll is triggered when the mouse is moved
// in Mark Text window.
// Scroll when dragging a tab to the beginning or end of the tab container.
this.autoScroller = autoScroll([ tabs ], {
margin: 20,
maxSpeed: 6,
scrollWhenOutside: false,
autoScroll: () => {
return this.autoScroller.down && drake.dragging
}
})
})
},
beforeDestroy () {
const tabs = this.$refs.tabContainer
tabs.removeEventListener('wheel', this.handleTabScroll)
if (this.autoScroller) {
// Force destroy
this.autoScroller.destroy(true)
}
if (this.drake) {
this.drake.destroy()
}
}
}
@ -54,18 +143,33 @@
fill: var(--themeColor);
}
.editor-tabs {
width: 100%;
position: relative;
display: flex;
flex-direction: row;
height: 35px;
user-select: none;
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, .1);
overflow: hidden;
&:hover > .new-file {
opacity: 1 !important;
}
}
.scrollable-tabs {
flex: 0 1 auto;
height: 35px;
overflow: hidden;
}
.tabs-container {
min-width: min-content;
list-style: none;
margin: 0;
padding: 0;
height: 35px;
position: relative;
display: flex;
flex-direction: row;
overflow: auto;
overflow-y: hidden;
z-index: 2;
&::-webkit-scrollbar:horizontal {
display: none;
}
@ -80,9 +184,15 @@
background: var(--floatBgColor);
display: flex;
align-items: center;
&[aria-grabbed="true"] {
color: var(--editorColor30) !important;
}
& > svg {
opacity: 0;
}
&:focus {
outline: none;
}
&:hover > svg {
opacity: 1;
}
@ -112,7 +222,8 @@
}
& > li.active {
background: var(--itemBgColor);
&:not(:last-child):after {
z-index: 3;
&:after {
content: '';
position: absolute;
left: 0;
@ -128,16 +239,39 @@
display: none;
}
}
& > li.new-file {
width: 35px;
height: 35px;
border-right: none;
background: transparent;
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
}
.editor-tabs > .new-file {
flex: 0 0 35px;
width: 35px;
height: 35px;
border-right: none;
background: transparent;
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
color: var(--editorColor50);
opacity: 0;
&.always-visible {
opacity: 1;
}
}
/* dragula effects */
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.8;
cursor: grabbing;
}
.gu-hide {
display: none !important;
}
.gu-unselectable {
user-select: none !important;
}
.gu-transit {
opacity: 0.2;
}
</style>

View File

@ -77,7 +77,7 @@
'rightColumn': state => state.layout.rightColumn,
'showSideBar': state => state.layout.showSideBar,
'projectTree': state => state.project.projectTree,
'sideBarWidth': state => state.project.sideBarWidth,
'sideBarWidth': state => state.layout.sideBarWidth,
'tabs': state => state.editor.tabs
}),
...mapGetters(['fileList']),
@ -101,7 +101,7 @@
const mouseUpHandler = event => {
document.removeEventListener('mousemove', mouseMoveHandler, false)
document.removeEventListener('mouseup', mouseUpHandler, false)
this.$store.dispatch('CHANGE_SIDE_BAR_WIDTH', sideBarWidth)
this.$store.dispatch('CHANGE_SIDE_BAR_WIDTH', sideBarWidth < 180 ? 180 : sideBarWidth)
}
const mouseMoveHandler = event => {
@ -141,7 +141,11 @@
<style scoped>
.side-bar {
display: flex;
flex-shrink: 0;
flex-grow: 0;
widows: 280px;
height: 100vh;
min-width: 180px;
position: relative;
color: var(--sideBarColor);
user-select: none;

View File

@ -1,24 +1,7 @@
<template>
<div class="tree-view">
<div class="title">
<a
href="javascript:;"
:class="{'active': active === 'tree'}"
title="Tree View"
>
<svg class="icon" aria-hidden="true" @click="active = 'tree'">
<use xlink:href="#icon-tree"></use>
</svg>
</a>
<a
href="javascript:;"
:class="{'active': active === 'list'}"
title="List View"
>
<svg class="icon" aria-hidden="true" @click="active = 'list'">
<use xlink:href="#icon-list"></use>
</svg>
</a>
<!-- Placeholder -->
</div>
<!-- opened files -->
<div class="opened-files">
@ -58,6 +41,24 @@
<use xlink:href="#icon-arrow"></use>
</svg>
<span class="default-cursor text-overflow" @click.stop="toggleDirectories()">{{ projectTree.name }}</span>
<a
href="javascript:;"
:class="{'active': active === 'tree'}"
title="Tree View"
>
<svg class="icon" aria-hidden="true" @click="active = 'tree'">
<use xlink:href="#icon-tree"></use>
</svg>
</a>
<a
href="javascript:;"
:class="{'active': active === 'list'}"
title="List View"
>
<svg class="icon" aria-hidden="true" @click="active = 'list'">
<use xlink:href="#icon-list"></use>
</svg>
</a>
</div>
<div class="tree-wrapper" v-show="showDirectories && active === 'tree'">
<folder
@ -226,26 +227,6 @@
padding: 0 15px;
display: flex;
flex-direction: row-reverse;
& > span {
flex: 1;
user-select: none;
}
& > a {
pointer-events: auto;
cursor: pointer;
margin-left: 8px;
color: var(--iconColor);
opacity: 0;
}
& > a:hover {
color: var(--themeColor);
}
& > a.active {
color: var(--themeColor);
}
}
.tree-view:hover .title a {
opacity: 1;
}
.icon-arrow {
@ -309,8 +290,26 @@
display: flex;
flex-direction: column;
& > .title {
padding-right: 15px;
display: flex;
align-items: center;
& > span {
flex: 1;
user-select: none;
}
& > a {
pointer-events: auto;
cursor: pointer;
margin-left: 8px;
color: var(--iconColor);
opacity: 0;
}
& > a:hover {
color: var(--themeColor);
}
& > a.active {
color: var(--themeColor);
}
}
& > .tree-wrapper,
& > .list-wrapper {
@ -322,6 +321,9 @@
}
flex: 1;
}
.project-tree div.title:hover > a {
opacity: 1;
}
.open-project {
flex: 1;
display: flex;

View File

@ -1,86 +1,96 @@
<template>
<div
class="title-bar"
:class="[{ 'active': active }, { 'tabs-visible': showTabBar }, { 'frameless': titleBarStyle === 'custom' }, { 'isOsx': platform === 'darwin' }]"
>
<div class="title">
<span v-if="!filename">Mark Text</span>
<span v-else>
<span
v-for="(path, index) of paths"
:key="index"
>
{{ path }}
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-arrow-right"></use>
</svg>
</span>
<span
class="filename"
:class="{'isOsx': platform === 'darwin'}"
@click="rename"
>
{{ filename }}
</span>
<span class="save-dot" :class="{'show': !isSaved}"></span>
</span>
</div>
<div :class="titleBarStyle === 'custom' ? 'left-toolbar title-no-drag' : 'right-toolbar'">
<div
v-if="titleBarStyle === 'custom'"
class="frameless-titlebar-menu title-no-drag"
@click.stop="handleMenuClick"
>&#9776;</div>
<el-tooltip
v-if="wordCount"
class="item"
:content="`${wordCount[show]} ${HASH[show].full + (wordCount[show] > 1 ? 's' : '')}`"
placement="bottom-end"
>
<div slot="content">
<div class="title-item">
<span class="front">Words:</span><span class="text">{{wordCount['word']}}</span>
</div>
<div class="title-item">
<span class="front">Characters:</span><span class="text">{{wordCount['character']}}</span>
</div>
<div class="title-item">
<span class="front">Paragraph:</span><span class="text">{{wordCount['paragraph']}}</span>
</div>
</div>
<div
v-if="wordCount"
class="word-count"
:class="[{ 'title-no-drag': platform !== 'darwin' }]"
@click.stop="handleWordClick"
>{{ `${HASH[show].short} ${wordCount[show]}` }}</div>
</el-tooltip>
</div>
<div>
<div
v-if="titleBarStyle === 'custom' && !isFullScreen"
class="right-toolbar"
:class="[{ 'title-no-drag': titleBarStyle === 'custom' }]"
class="title-bar-editor-bg"
:class="{ 'tabs-visible': showTabBar }"
></div>
<div
class="title-bar"
:class="[{ 'active': active }, { 'tabs-visible': showTabBar }, { 'frameless': titleBarStyle === 'custom' }, { 'isOsx': platform === 'darwin' }]"
>
<div class="frameless-titlebar-button frameless-titlebar-close" @click.stop="handleCloseClick">
<div>
<svg width="10" height="10">
<path :d="windowIconClose" />
</svg>
</div>
<div class="title">
<span v-if="!filename">Mark Text</span>
<span v-else>
<span
v-for="(path, index) of paths"
:key="index"
>
{{ path }}
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-arrow-right"></use>
</svg>
</span>
<span
class="filename"
:class="{'isOsx': platform === 'darwin'}"
@click="rename"
>
{{ filename }}
</span>
<span class="save-dot" :class="{'show': !isSaved}"></span>
</span>
</div>
<div class="frameless-titlebar-button frameless-titlebar-toggle" @click.stop="handleMaximizeClick">
<div>
<svg width="10" height="10">
<path v-show="!isMaximized" :d="windowIconMaximize" />
<path v-show="isMaximized" :d="windowIconRestore" />
</svg>
<div :class="titleBarStyle === 'custom' ? 'left-toolbar title-no-drag' : 'right-toolbar'">
<div
v-if="titleBarStyle === 'custom'"
class="frameless-titlebar-menu title-no-drag"
@click.stop="handleMenuClick"
>
<span class="text-center-vertical">&#9776;</span>
</div>
<el-tooltip
v-if="wordCount"
class="item"
:content="`${wordCount[show]} ${HASH[show].full + (wordCount[show] > 1 ? 's' : '')}`"
placement="bottom-end"
>
<div slot="content">
<div class="title-item">
<span class="front">Words:</span><span class="text">{{wordCount['word']}}</span>
</div>
<div class="title-item">
<span class="front">Characters:</span><span class="text">{{wordCount['character']}}</span>
</div>
<div class="title-item">
<span class="front">Paragraph:</span><span class="text">{{wordCount['paragraph']}}</span>
</div>
</div>
<div
v-if="wordCount"
class="word-count"
:class="[{ 'title-no-drag': platform !== 'darwin' }]"
@click.stop="handleWordClick"
>
<span class="text-center-vertical">{{ `${HASH[show].short} ${wordCount[show]}` }}</span>
</div>
</el-tooltip>
</div>
<div class="frameless-titlebar-button frameless-titlebar-minimize" @click.stop="handleMinimizeClick">
<div>
<svg width="10" height="10">
<path :d="windowIconMinimize" />
</svg>
<div
v-if="titleBarStyle === 'custom' && !isFullScreen"
class="right-toolbar"
:class="[{ 'title-no-drag': titleBarStyle === 'custom' }]"
>
<div class="frameless-titlebar-button frameless-titlebar-close" @click.stop="handleCloseClick">
<div>
<svg width="10" height="10">
<path :d="windowIconClose" />
</svg>
</div>
</div>
<div class="frameless-titlebar-button frameless-titlebar-toggle" @click.stop="handleMaximizeClick">
<div>
<svg width="10" height="10">
<path v-show="!isMaximized" :d="windowIconMaximize" />
<path v-show="isMaximized" :d="windowIconRestore" />
</svg>
</div>
</div>
<div class="frameless-titlebar-button frameless-titlebar-minimize" @click.stop="handleMinimizeClick">
<div>
<svg width="10" height="10">
<path :d="windowIconMinimize" />
</svg>
</div>
</div>
</div>
</div>
@ -186,17 +196,11 @@
},
handleMenuClick () {
let offsetX = 23
const elems = document.getElementsByClassName('side-bar')
if (elems) {
offsetX += elems[0].clientWidth
}
const win = remote.getCurrentWindow()
remote
.Menu
.getApplicationMenu()
.popup({ window: win, x: offsetX, y: 20 })
.popup({ window: win, x: 23, y: 20 })
},
handleWindowStateChanged () {
@ -220,15 +224,23 @@
</script>
<style scoped>
.title-bar-editor-bg {
height: var(--titleBarHeight);
background: var(--editorBgColor);
position: relative;
left: 0;
top: 0;
right: 0;
}
.title-bar {
-webkit-app-region: drag;
user-select: none;
background: var(--editorBgColor);
width: 100%;
background: transparent;
height: var(--titleBarHeight);
box-sizing: border-box;
color: var(--editorColor50);
position: absolute;
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 2;
@ -244,7 +256,7 @@
vertical-align: top;
}
.title {
padding: 0 100px;
padding: 0 142px;
height: 100%;
line-height: var(--titleBarHeight);
font-size: 14px;
@ -293,27 +305,27 @@
color: var(sideBarTitleColor);
}
.right-toolbar {
padding: 0 10px;
height: 100%;
position: absolute;
top: 0;
right: 0;
width: 100px;
display: flex;
align-items: center;
flex-direction: row-reverse;
}
.left-toolbar {
padding: 0 10px;
height: 100%;
position: absolute;
top: 0;
left: 0;
width: 100px;
width: 118px; /* + 2*10px padding*/
display: flex;
flex-direction: row;
}
.right-toolbar {
height: 100%;
position: absolute;
top: 0;
right: 0;
width: 138px;
display: flex;
align-items: center;
flex-direction: row-reverse;
}
.word-count {
cursor: pointer;
font-size: 14px;
@ -326,11 +338,11 @@
border-radius: 4px;
transition: all .25s ease-in-out;
}
.word-count:hover {
background: var(--sideBarBgColor);
color: var(--sideBarTitleColor);
}
.title-no-drag {
-webkit-app-region: no-drag;
}
@ -338,7 +350,7 @@
.frameless-titlebar-button {
position: relative;
display: block;
width: var(--titleBarHeight);
width: 46px;
height: var(--titleBarHeight);
}
.frameless-titlebar-button > div {
@ -364,6 +376,12 @@
.frameless-titlebar-close:hover svg {
fill: #ffffff
}
.text-center-vertical {
display: inline-block;
vertical-align: middle;
line-height: normal;
}
</style>
<style>

View File

@ -51,6 +51,31 @@ const mutations = {
}
}
},
// Exchange from with to and move from to the end if to is null or empty.
EXCHANGE_TABS_BY_ID (state, tabIDs) {
const { fromId } = tabIDs
const toId = tabIDs.toId // may be null
const { tabs } = state
const moveItem = (arr, from, to) => {
if (from === to) return true
const len = arr.length
const item = arr.splice(from, 1)
if (item.length === 0) return false
arr.splice(to, 0, item[0])
return arr.length === len
}
const fromIndex = tabs.findIndex(t => t.id === fromId)
if (!toId) {
moveItem(tabs, fromIndex, tabs.length - 1)
} else {
const toIndex = tabs.findIndex(t => t.id === toId)
const realToIndex = fromIndex < toIndex ? toIndex - 1 : toIndex
moveItem(tabs, fromIndex, realToIndex)
}
},
LOAD_CHANGE (state, change) {
const { tabs, currentFile } = state
const { data, pathname } = change
@ -233,6 +258,10 @@ const actions = {
dispatch('ASK_FILE_WATCH', { pathname, watch: false })
},
EXCHANGE_TABS_BY_ID ({ commit }, tabIDs) {
commit('EXCHANGE_TABS_BY_ID', tabIDs)
},
// need update line ending when change between windows.
LISTEN_FOR_LINEENDING_MENU ({ commit, state, dispatch }) {
ipcRenderer.on('AGANI::req-update-line-ending-menu', e => {

View File

@ -1,10 +1,14 @@
import { ipcRenderer } from 'electron'
const width = localStorage.getItem('side-bar-width')
const sideBarWidth = typeof +width === 'number' ? Math.max(+width, 180) : 280
// messages from main process, and do not change the state
const state = {
rightColumn: 'files',
showSideBar: false,
showTabBar: false
showTabBar: false,
sideBarWidth
}
const getters = {}
@ -12,6 +16,11 @@ const getters = {}
const mutations = {
SET_LAYOUT (state, layout) {
Object.assign(state, layout)
},
SET_SIDE_BAR_WIDTH (state, width) {
// TODO: Add side bar with to session (GH#732).
localStorage.setItem('side-bar-width', Math.max(+width, 180))
state.sideBarWidth = width
}
}
@ -29,6 +38,9 @@ const actions = {
SET_LAYOUT_MENU_ITEM ({ commit, state }) {
const { showTabBar, showSideBar } = state
ipcRenderer.send('AGANI::set-view-layout', { showTabBar, showSideBar })
},
CHANGE_SIDE_BAR_WIDTH ({ commit }, width) {
commit('SET_SIDE_BAR_WIDTH', width)
}
}

View File

@ -7,11 +7,7 @@ import { PATH_SEPARATOR } from '../config'
import notice from '../services/notification'
import { getFileStateFromData } from './help'
const width = localStorage.getItem('side-bar-width')
const sideBarWidth = typeof +width === 'number' ? Math.max(+width, 180) : 280
const state = {
sideBarWidth,
activeItem: {},
createCache: {},
// Use to cache newly created filename, for open iimmediately.
@ -56,10 +52,6 @@ const mutations = {
files: []
}
},
SET_SIDE_BAR_WIDTH (state, width) {
localStorage.setItem('side-bar-width', Math.max(+width, 180))
state.sideBarWidth = width
},
SET_NEWFILENAME (state, name) {
state.newFileNameCache = name
},
@ -162,9 +154,6 @@ const actions = {
}
})
},
CHANGE_SIDE_BAR_WIDTH ({ commit }, width) {
commit('SET_SIDE_BAR_WIDTH', width)
},
CHANGE_ACTIVE_ITEM ({ commit }, activeItem) {
commit('SET_ACTIVE_ITEM', activeItem)
},

113
yarn.lock
View File

@ -446,6 +446,11 @@ amdefine@>=0.0.4:
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=
animation-frame-polyfill@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/animation-frame-polyfill/-/animation-frame-polyfill-1.0.1.tgz#5f5ad993a78794bd176acde5b6dce62867410c9d"
integrity sha1-X1rZk6eHlL0Xas3lttzmKGdBDJ0=
ansi-align@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
@ -634,6 +639,11 @@ array-flatten@^2.1.0:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
array-from@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
array-includes@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
@ -761,6 +771,11 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
atoa@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/atoa/-/atoa-1.0.0.tgz#0cc0e91a480e738f923ebc103676471779b34a49"
integrity sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk=
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
@ -2604,6 +2619,14 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
contra@1.9.4:
version "1.9.4"
resolved "https://registry.yarnpkg.com/contra/-/contra-1.9.4.tgz#f53bde42d7e5b5985cae4d99a8d610526de8f28d"
integrity sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0=
dependencies:
atoa "1.0.0"
ticky "1.0.1"
convert-source-map@^1.5.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
@ -2739,6 +2762,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
create-point-cb@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/create-point-cb/-/create-point-cb-1.2.0.tgz#1bce47fc4fc01855ee12138d676b0cb2a7cbce71"
integrity sha1-G85H/E/AGFXuEhONZ2sMsqfLznE=
dependencies:
type-func "^1.0.1"
cross-env@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2"
@ -2772,6 +2802,13 @@ cross-unzip@0.0.2:
resolved "https://registry.yarnpkg.com/cross-unzip/-/cross-unzip-0.0.2.tgz#5183bc47a09559befcf98cc4657964999359372f"
integrity sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8=
crossvent@1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/crossvent/-/crossvent-1.5.4.tgz#da2c4f8f40c94782517bf2beec1044148194ab92"
integrity sha1-2ixPj0DJR4JRe/K+7BBEFIGUq5I=
dependencies:
custom-event "1.0.0"
crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@ -2947,6 +2984,11 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
custom-event@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.0.tgz#2e4628be19dc4b214b5c02630c5971e811618062"
integrity sha1-LkYovhncSyFLXAJjDFlx6BFhgGI=
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@ -3590,6 +3632,18 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-autoscroller@^2.3.4:
version "2.3.4"
resolved "https://registry.yarnpkg.com/dom-autoscroller/-/dom-autoscroller-2.3.4.tgz#1ed25cbde2bdf3bf3eb762937089b20ecef190bd"
integrity sha512-HcAdt/2Dq9x4CG6LWXc2x9Iq0MJPAu8fuzHncclq7byufqYEYVtx9sZ/dyzR+gdj4qwEC9p27Lw1G2HRRYX6jQ==
dependencies:
animation-frame-polyfill "^1.0.0"
create-point-cb "^1.0.0"
dom-mousemove-dispatcher "^1.0.1"
dom-plane "^1.0.1"
dom-set "^1.0.1"
type-func "^1.0.1"
dom-converter@^0.2:
version "0.2.0"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@ -3597,6 +3651,18 @@ dom-converter@^0.2:
dependencies:
utila "~0.4"
dom-mousemove-dispatcher@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dom-mousemove-dispatcher/-/dom-mousemove-dispatcher-1.0.1.tgz#a24a6ddf93b27bb3694f72087546a57fc7e9140f"
integrity sha1-okpt35Oye7NpT3IIdUalf8fpFA8=
dom-plane@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/dom-plane/-/dom-plane-1.0.2.tgz#f8c85e697c587f147e8fc2fac1de078c1fe4172c"
integrity sha1-+MheaXxYfxR+j8L6wd4HjB/kFyw=
dependencies:
create-point-cb "^1.0.0"
dom-serialize@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
@ -3615,6 +3681,15 @@ dom-serializer@0, dom-serializer@~0.1.1:
domelementtype "^1.3.0"
entities "^1.1.1"
dom-set@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/dom-set/-/dom-set-1.1.1.tgz#5c2c610ee4839b520ed5f98ddbcbe314c0fa954a"
integrity sha1-XCxhDuSDm1IO1fmN28vjFMD6lUo=
dependencies:
array-from "^2.1.1"
is-array "^1.0.1"
iselement "^1.1.4"
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@ -3694,6 +3769,14 @@ dotenv@^7.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c"
integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==
dragula@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/dragula/-/dragula-3.7.2.tgz#4a35c9d3981ffac1a949c29ca7285058e87393ce"
integrity sha1-SjXJ05gf+sGpScKcpyhQWOhzk84=
dependencies:
contra "1.9.4"
crossvent "1.5.4"
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -5743,6 +5826,11 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-array@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a"
integrity sha1-6YUMwsyGDDvAl36EzPDdRkWEJ5o=
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -6039,6 +6127,11 @@ isbinaryfile@^4.0.0:
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.0.tgz#07d1061c21598b41292b0f5c68add5eab601ad8e"
integrity sha512-RBtmso6l2mCaEsUvXngMTIjg3oheXo0MgYzzfT6sk44RYggPnm9fT+cQJAmzRnJIxPHXg9FZglqDJGW28dvcqA==
iselement@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/iselement/-/iselement-1.1.4.tgz#7e55b52a8ebca50a7e2e80e5b8d2840f32353146"
integrity sha1-flW1Ko68pQp+LoDluNKEDzI1MUY=
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@ -10324,6 +10417,11 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826"
integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==
ticky@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ticky/-/ticky-1.0.1.tgz#b7cfa71e768f1c9000c497b9151b30947c50e46d"
integrity sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0=
timed-out@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
@ -10531,7 +10629,20 @@ type-detect@^4.0.0, type-detect@^4.0.5:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
type-is@~1.6.16, type-is@~1.6.17:
type-func@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/type-func/-/type-func-1.0.3.tgz#ab184234ae80d8d50057cefeff3b2d97d08ae9b0"
integrity sha1-qxhCNK6A2NUAV87+/zstl9CK6bA=
type-is@~1.6.16:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.18"
type-is@~1.6.17:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==