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** **:cactus:Feature**
- The cursor jump to the end of format or to the next brackets when press `tab`(#976) - 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** **:butterfly:Optimization**

View File

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

View File

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

View File

@ -48,9 +48,27 @@
} }
.editor-tabs { .editor-tabs {
border-bottom: 1px solid #1d1d1d;
box-shadow: none !important; 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,
.tabs-container > li.active { .tabs-container > li.active {
background: var(--editorBgColor) !important; background: var(--editorBgColor) !important;

View File

@ -27,15 +27,32 @@
--editorAreaWidth: 700px; --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; background: #f3f3f3 !important;
} }
.editor-tabs {
background: #f3f3f3 !important;
border-bottom: 1px solid #dddddd !important;
box-shadow: none !important;
}
.tabs-container > li { .tabs-container > li {
background: none !important; background: none !important;
} }
@ -45,7 +62,7 @@
border-bottom: none; border-bottom: none;
background: var(--floatBgColor) !important; background: var(--floatBgColor) !important;
} }
.tabs-container > li.active:not(:last-child):after { .tabs-container > li.active:after {
top: 0 !important; top: 0 !important;
bottom: auto !important; bottom: auto !important;
background: var(--themeColor) !important; background: var(--themeColor) !important;

View File

@ -110,9 +110,27 @@
} }
.editor-tabs { .editor-tabs {
border-bottom: 1px solid #181a1f;
box-shadow: none !important; 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,
.tabs-container > li.active { .tabs-container > li.active {
background: var(--editorBgColor) !important; background: var(--editorBgColor) !important;
@ -121,7 +139,7 @@
border: 1px solid #181a1f; border: 1px solid #181a1f;
border-bottom: none; border-bottom: none;
} }
.tabs-container > li.active:not(:last-child):after { .tabs-container > li.active:after {
top: 0 !important; top: 0 !important;
right: auto !important; right: auto !important;
width: 2px !important; width: 2px !important;

View File

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

View File

@ -68,13 +68,12 @@
<style scoped> <style scoped>
.editor-with-tabs { .editor-with-tabs {
width: 100%; position: relative;
height: 100%; height: 100%;
padding-top: var(--titleBarHeight);
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
background: var(--editorBgColor); background: var(--editorBgColor);
& > .container { & > .container {

View File

@ -1,13 +1,22 @@
<template> <template>
<div
class="editor-tabs"
:style="{'max-width': showSideBar ? `calc(100vw - ${sideBarWidth}px` : '100vw' }"
>
<div <div
class="editor-tabs" class="scrollable-tabs"
ref="tabContainer"
> >
<ul class="tabs-container"> <ul
ref="tabDropContainer"
class="tabs-container"
>
<li <li
:title="file.pathname" :title="file.pathname"
:class="{'active': currentFile.id === file.id, 'unsaved': !file.isSaved }" :class="{'active': currentFile.id === file.id, 'unsaved': !file.isSaved }"
v-for="(file, index) of tabs" v-for="file of tabs"
:key="index" :key="file.id"
:data-id="file.id"
@click.stop="selectFile(file)" @click.stop="selectFile(file)"
> >
<span>{{ file.filename }}</span> <span>{{ file.filename }}</span>
@ -18,32 +27,112 @@
<use id="default-close-icon" xlink:href="#icon-close-small"></use> <use id="default-close-icon" xlink:href="#icon-close-small"></use>
</svg> </svg>
</li> </li>
<li class="new-file">
<svg class="icon" aria-hidden="true"
@click.stop="newFile()"
>
<use xlink:href="#icon-plus"></use>
</svg>
</li>
</ul> </ul>
</div> </div>
<div
class="new-file"
>
<svg class="icon" aria-hidden="true"
@click.stop="newFile()"
>
<use xlink:href="#icon-plus"></use>
</svg>
</div>
</div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import autoScroll from 'dom-autoscroller'
import dragula from 'dragula'
import { tabsMixins } from '../../mixins' import { tabsMixins } from '../../mixins'
export default { export default {
data () {
this.autoScroller = null
this.drake = null
return {}
},
mixins: [tabsMixins], mixins: [tabsMixins],
computed: { computed: {
...mapState({ ...mapState({
currentFile: state => state.editor.currentFile, 'currentFile': state => state.editor.currentFile,
tabs: state => state.editor.tabs 'tabs': state => state.editor.tabs,
'showSideBar': state => state.layout.showSideBar,
'sideBarWidth': state => state.layout.sideBarWidth
}) })
}, },
methods: { methods: {
newFile () { newFile () {
this.$store.dispatch('NEW_BLANK_FILE') 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); fill: var(--themeColor);
} }
.editor-tabs { .editor-tabs {
width: 100%; position: relative;
display: flex;
flex-direction: row;
height: 35px; height: 35px;
user-select: none; user-select: none;
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, .1); 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 { .tabs-container {
min-width: min-content;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 35px;
position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
overflow: auto; overflow-y: hidden;
z-index: 2;
&::-webkit-scrollbar:horizontal { &::-webkit-scrollbar:horizontal {
display: none; display: none;
} }
@ -80,9 +184,15 @@
background: var(--floatBgColor); background: var(--floatBgColor);
display: flex; display: flex;
align-items: center; align-items: center;
&[aria-grabbed="true"] {
color: var(--editorColor30) !important;
}
& > svg { & > svg {
opacity: 0; opacity: 0;
} }
&:focus {
outline: none;
}
&:hover > svg { &:hover > svg {
opacity: 1; opacity: 1;
} }
@ -112,7 +222,8 @@
} }
& > li.active { & > li.active {
background: var(--itemBgColor); background: var(--itemBgColor);
&:not(:last-child):after { z-index: 3;
&:after {
content: ''; content: '';
position: absolute; position: absolute;
left: 0; left: 0;
@ -128,16 +239,39 @@
display: none; display: none;
} }
} }
}
& > li.new-file { .editor-tabs > .new-file {
width: 35px; flex: 0 0 35px;
height: 35px; width: 35px;
border-right: none; height: 35px;
background: transparent; border-right: none;
display: flex; background: transparent;
align-items: center; display: flex;
justify-content: space-around; align-items: center;
cursor: pointer; 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> </style>

View File

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

View File

@ -1,24 +1,7 @@
<template> <template>
<div class="tree-view"> <div class="tree-view">
<div class="title"> <div class="title">
<a <!-- Placeholder -->
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>
<!-- opened files --> <!-- opened files -->
<div class="opened-files"> <div class="opened-files">
@ -50,7 +33,7 @@
</div> </div>
</div> </div>
<!-- project tree view --> <!-- project tree view -->
<div <div
class="project-tree" v-if="projectTree" class="project-tree" v-if="projectTree"
> >
<div class="title"> <div class="title">
@ -58,6 +41,24 @@
<use xlink:href="#icon-arrow"></use> <use xlink:href="#icon-arrow"></use>
</svg> </svg>
<span class="default-cursor text-overflow" @click.stop="toggleDirectories()">{{ projectTree.name }}</span> <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>
<div class="tree-wrapper" v-show="showDirectories && active === 'tree'"> <div class="tree-wrapper" v-show="showDirectories && active === 'tree'">
<folder <folder
@ -226,26 +227,6 @@
padding: 0 15px; padding: 0 15px;
display: flex; display: flex;
flex-direction: row-reverse; 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 { .icon-arrow {
@ -309,8 +290,26 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
& > .title { & > .title {
padding-right: 15px;
display: flex; display: flex;
align-items: center; 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, & > .tree-wrapper,
& > .list-wrapper { & > .list-wrapper {
@ -322,6 +321,9 @@
} }
flex: 1; flex: 1;
} }
.project-tree div.title:hover > a {
opacity: 1;
}
.open-project { .open-project {
flex: 1; flex: 1;
display: flex; display: flex;

View File

@ -1,86 +1,96 @@
<template> <template>
<div <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="title-bar-editor-bg"
class="right-toolbar" :class="{ 'tabs-visible': showTabBar }"
:class="[{ 'title-no-drag': titleBarStyle === 'custom' }]" ></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 class="title">
<div> <span v-if="!filename">Mark Text</span>
<svg width="10" height="10"> <span v-else>
<path :d="windowIconClose" /> <span
</svg> v-for="(path, index) of paths"
</div> :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>
<div class="frameless-titlebar-button frameless-titlebar-toggle" @click.stop="handleMaximizeClick"> <div :class="titleBarStyle === 'custom' ? 'left-toolbar title-no-drag' : 'right-toolbar'">
<div> <div
<svg width="10" height="10"> v-if="titleBarStyle === 'custom'"
<path v-show="!isMaximized" :d="windowIconMaximize" /> class="frameless-titlebar-menu title-no-drag"
<path v-show="isMaximized" :d="windowIconRestore" /> @click.stop="handleMenuClick"
</svg> >
<span class="text-center-vertical">&#9776;</span>
</div> </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>
<div class="frameless-titlebar-button frameless-titlebar-minimize" @click.stop="handleMinimizeClick"> <div
<div> v-if="titleBarStyle === 'custom' && !isFullScreen"
<svg width="10" height="10"> class="right-toolbar"
<path :d="windowIconMinimize" /> :class="[{ 'title-no-drag': titleBarStyle === 'custom' }]"
</svg> >
<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> </div>
</div> </div>
@ -186,17 +196,11 @@
}, },
handleMenuClick () { handleMenuClick () {
let offsetX = 23
const elems = document.getElementsByClassName('side-bar')
if (elems) {
offsetX += elems[0].clientWidth
}
const win = remote.getCurrentWindow() const win = remote.getCurrentWindow()
remote remote
.Menu .Menu
.getApplicationMenu() .getApplicationMenu()
.popup({ window: win, x: offsetX, y: 20 }) .popup({ window: win, x: 23, y: 20 })
}, },
handleWindowStateChanged () { handleWindowStateChanged () {
@ -220,15 +224,23 @@
</script> </script>
<style scoped> <style scoped>
.title-bar-editor-bg {
height: var(--titleBarHeight);
background: var(--editorBgColor);
position: relative;
left: 0;
top: 0;
right: 0;
}
.title-bar { .title-bar {
-webkit-app-region: drag; -webkit-app-region: drag;
user-select: none; user-select: none;
background: var(--editorBgColor); background: transparent;
width: 100%;
height: var(--titleBarHeight); height: var(--titleBarHeight);
box-sizing: border-box; box-sizing: border-box;
color: var(--editorColor50); color: var(--editorColor50);
position: absolute; position: fixed;
left: 0;
top: 0; top: 0;
right: 0; right: 0;
z-index: 2; z-index: 2;
@ -244,7 +256,7 @@
vertical-align: top; vertical-align: top;
} }
.title { .title {
padding: 0 100px; padding: 0 142px;
height: 100%; height: 100%;
line-height: var(--titleBarHeight); line-height: var(--titleBarHeight);
font-size: 14px; font-size: 14px;
@ -293,27 +305,27 @@
color: var(sideBarTitleColor); 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 { .left-toolbar {
padding: 0 10px; padding: 0 10px;
height: 100%; height: 100%;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100px; width: 118px; /* + 2*10px padding*/
display: flex; display: flex;
flex-direction: row; 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 { .word-count {
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
@ -326,11 +338,11 @@
border-radius: 4px; border-radius: 4px;
transition: all .25s ease-in-out; transition: all .25s ease-in-out;
} }
.word-count:hover { .word-count:hover {
background: var(--sideBarBgColor); background: var(--sideBarBgColor);
color: var(--sideBarTitleColor); color: var(--sideBarTitleColor);
} }
.title-no-drag { .title-no-drag {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
@ -338,7 +350,7 @@
.frameless-titlebar-button { .frameless-titlebar-button {
position: relative; position: relative;
display: block; display: block;
width: var(--titleBarHeight); width: 46px;
height: var(--titleBarHeight); height: var(--titleBarHeight);
} }
.frameless-titlebar-button > div { .frameless-titlebar-button > div {
@ -364,6 +376,12 @@
.frameless-titlebar-close:hover svg { .frameless-titlebar-close:hover svg {
fill: #ffffff fill: #ffffff
} }
.text-center-vertical {
display: inline-block;
vertical-align: middle;
line-height: normal;
}
</style> </style>
<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) { LOAD_CHANGE (state, change) {
const { tabs, currentFile } = state const { tabs, currentFile } = state
const { data, pathname } = change const { data, pathname } = change
@ -233,6 +258,10 @@ const actions = {
dispatch('ASK_FILE_WATCH', { pathname, watch: false }) 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. // need update line ending when change between windows.
LISTEN_FOR_LINEENDING_MENU ({ commit, state, dispatch }) { LISTEN_FOR_LINEENDING_MENU ({ commit, state, dispatch }) {
ipcRenderer.on('AGANI::req-update-line-ending-menu', e => { ipcRenderer.on('AGANI::req-update-line-ending-menu', e => {

View File

@ -1,10 +1,14 @@
import { ipcRenderer } from 'electron' 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 // messages from main process, and do not change the state
const state = { const state = {
rightColumn: 'files', rightColumn: 'files',
showSideBar: false, showSideBar: false,
showTabBar: false showTabBar: false,
sideBarWidth
} }
const getters = {} const getters = {}
@ -12,6 +16,11 @@ const getters = {}
const mutations = { const mutations = {
SET_LAYOUT (state, layout) { SET_LAYOUT (state, layout) {
Object.assign(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 }) { SET_LAYOUT_MENU_ITEM ({ commit, state }) {
const { showTabBar, showSideBar } = state const { showTabBar, showSideBar } = state
ipcRenderer.send('AGANI::set-view-layout', { showTabBar, showSideBar }) 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 notice from '../services/notification'
import { getFileStateFromData } from './help' import { getFileStateFromData } from './help'
const width = localStorage.getItem('side-bar-width')
const sideBarWidth = typeof +width === 'number' ? Math.max(+width, 180) : 280
const state = { const state = {
sideBarWidth,
activeItem: {}, activeItem: {},
createCache: {}, createCache: {},
// Use to cache newly created filename, for open iimmediately. // Use to cache newly created filename, for open iimmediately.
@ -56,10 +52,6 @@ const mutations = {
files: [] files: []
} }
}, },
SET_SIDE_BAR_WIDTH (state, width) {
localStorage.setItem('side-bar-width', Math.max(+width, 180))
state.sideBarWidth = width
},
SET_NEWFILENAME (state, name) { SET_NEWFILENAME (state, name) {
state.newFileNameCache = 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) { CHANGE_ACTIVE_ITEM ({ commit }, activeItem) {
commit('SET_ACTIVE_ITEM', 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" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= 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: ansi-align@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" 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" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== 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: array-includes@^3.0.3:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" 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" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 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: atob@^2.1.1:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" 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" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 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: convert-source-map@^1.5.1:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" 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" safe-buffer "^5.0.1"
sha.js "^2.4.8" 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: cross-env@^5.2.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2" 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" resolved "https://registry.yarnpkg.com/cross-unzip/-/cross-unzip-0.0.2.tgz#5183bc47a09559befcf98cc4657964999359372f"
integrity sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8= 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: crypto-browserify@^3.11.0:
version "3.12.0" version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@ -2947,6 +2984,11 @@ currently-unhandled@^0.4.1:
dependencies: dependencies:
array-find-index "^1.0.1" 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: custom-event@~1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@ -3590,6 +3632,18 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" 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: dom-converter@^0.2:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@ -3597,6 +3651,18 @@ dom-converter@^0.2:
dependencies: dependencies:
utila "~0.4" 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: dom-serialize@^2.2.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" 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" domelementtype "^1.3.0"
entities "^1.1.1" 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: domain-browser@^1.1.1:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" 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" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c"
integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== 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: duplexer3@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -5743,6 +5826,11 @@ is-accessor-descriptor@^1.0.0:
dependencies: dependencies:
kind-of "^6.0.0" 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: is-arrayish@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 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" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.0.tgz#07d1061c21598b41292b0f5c68add5eab601ad8e"
integrity sha512-RBtmso6l2mCaEsUvXngMTIjg3oheXo0MgYzzfT6sk44RYggPnm9fT+cQJAmzRnJIxPHXg9FZglqDJGW28dvcqA== 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: isexe@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 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" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826"
integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== 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: timed-out@^4.0.0:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" 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" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 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" version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==