draggable/nicer-looking tabs + icon

This commit is contained in:
dragonwocky 2020-10-09 20:42:34 +11:00
parent 5ae9aed260
commit 09069e03e9
Signed by: dragonwocky
GPG Key ID: C7A48B7846AA706D
5 changed files with 148 additions and 77 deletions

View File

@ -154,9 +154,8 @@ module.exports = (store, __exports) => {
'--theme--interactive_hover-border',
'--theme--button_close',
'--theme--button_close-fill',
'--theme--option_active-background',
'--theme--selected',
'--theme--option_hover-color',
'--theme--option_hover-background',
'--theme--text',
].map((rule) => [rule, getStyle(rule)])
);

View File

@ -11,7 +11,7 @@
word-break: break-word;
text-decoration: none;
text-size-adjust: 100%;
font-family: var(--theme--font_sans);
font-family: var(--theme--font_sans) !important;
outline-color: var(--theme--table-border);
}
@ -65,7 +65,7 @@ body[style*='--theme']::after {
html,
body,
#root {
background: var(--theme--main);
background: var(--theme--main) !important;
position: relative;
height: 100%;
margin: 0;
@ -105,6 +105,16 @@ body,
#tabs {
margin-top: auto;
}
#tabs::before {
content: '';
height: 1.25em;
width: 1.25em;
display: inline-block;
margin: auto 1em -0.25em 1em;
background-size: contain;
background-image: url('enhancement://core/icons/mac+linux.png');
background-repeat: no-repeat;
}
#tabs .tab {
display: inline-flex;
background: var(--theme--main);
@ -113,6 +123,7 @@ body,
padding: 0.2em 0.4em;
text-align: left;
border-bottom: 4px solid var(--theme--table-border);
opacity: 0.8;
}
#tabs .tab:first-child {
margin-top: 0.5em;
@ -122,6 +133,8 @@ body,
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#tabs .tab.slideIn span:not(.close) {
animation: tabSlideIn 100ms ease-in-out;
}
#tabs .tab .close {
@ -130,7 +143,9 @@ body,
font-weight: bold;
}
#tabs .tab.current {
opacity: 1;
background: var(--theme--selected);
border-bottom: 4px solid var(--theme--option_active-background);
}
#tabs .tab.new {
background: none;
@ -141,12 +156,17 @@ body,
padding: 0 0.35em 0.1em 0.3em;
font-weight: bold;
}
#tabs .tab:hover {
opacity: 1;
}
#tabs .tab .close:hover,
#tabs .tab.new span:hover {
background: var(--theme--option_hover-background);
color: var(--theme--option_hover-color);
background: var(--theme--table-border);
border-radius: 5px;
}
#tabs .tab.dragged-over {
box-shadow: inset 4px 0 0 0 var(--theme--selected);
}
.notion {
z-index: 2;

View File

@ -40,6 +40,7 @@ module.exports = (store, __exports) => {
tabs: new Map([[0, true]]),
};
this.$titlebar = null;
this.$dragging = null;
this.views = {
current: {
$el: () => this.views.html[this.views.current.id],
@ -64,79 +65,56 @@ module.exports = (store, __exports) => {
this.prevSearch = this.prevSearch.bind(this);
this.clearSearch = this.clearSearch.bind(this);
this.doneSearch = this.doneSearch.bind(this);
// draggable re-ordering
const getTab = ($el) => {
if ($el.innerText === '+')
return [null, document.querySelector('.tab.new')];
if ($el.innerText === '×') $el = $el.parentElement;
if (!$el.innerText.endsWith('×')) $el = $el.parentElement;
return (
Object.entries(this.views.tabs).find(
([id, $node]) => $node === $el
) || []
);
};
document.addEventListener('dragstart', (event) => {
if (!this.$titlebar) return;
this.$dragging = getTab(event.target)[0];
event.target.style.opacity = 0.5;
});
document.addEventListener('dragend', (event) => {
if (!this.$titlebar) return;
event.target.style.opacity = '';
});
document.addEventListener('dragover', (event) => {
if (!this.$titlebar) return;
event.preventDefault();
document
.querySelectorAll('.dragged-over')
.forEach((el) => el.classList.remove('dragged-over'));
const tab = getTab(event.target);
if (tab[1]) tab[1].classList.add('dragged-over');
});
document.addEventListener('drop', (event) => {
if (!this.$titlebar || this.$dragging === null) return;
event.preventDefault();
document
.querySelectorAll('.dragged-over')
.forEach((el) => el.classList.remove('dragged-over'));
const from = getTab(this.views.tabs[+this.$dragging]),
to = getTab(event.target);
if (!from[1].classList.contains('new') && from[0] !== to[0])
to[1].parentElement.insertBefore(from[1], to[1]);
from[1].classList.remove('slideIn');
this.$dragging = null;
});
}
componentDidMount() {
const buttons = require('./buttons.js')(store);
this.$titlebar.appendChild(buttons.element);
this.loadListeners();
}
newTab() {
let id = 0,
state = new Map(this.state.tabs);
while (this.state.tabs.get(id)) id++;
state.delete(id);
if (this.views.html[id]) {
this.views.html[id].style.opacity = '0';
let unhide;
unhide = () => {
this.views.html[id].style.opacity = '';
this.views.html[id].removeEventListener('did-stop-loading', unhide);
};
this.views.html[id].addEventListener('did-stop-loading', unhide);
this.views.html[id].loadURL(this.views.current.$el().src);
}
this.openTab(id, state);
}
openTab(id, state = new Map(this.state.tabs)) {
if (!id && id !== 0) return;
this.views.current.id = id;
this.setState({ tabs: state.set(id, true) }, this.focusTab.bind(this));
}
closeTab(id) {
if ((!id && id !== 0) || !this.state.tabs.get(id)) return;
if ([...this.state.tabs].filter(([id, open]) => open).length === 1)
return electron.remote.getCurrentWindow().close();
while (
!this.state.tabs.get(this.views.current.id) ||
this.views.current.id === id
) {
this.views.current.id = this.views.current.id - 1;
if (this.views.current.id < 0)
this.views.current.id = this.state.tabs.size - 1;
}
this.setState(
{ tabs: new Map(this.state.tabs).set(id, false) },
this.focusTab.bind(this)
);
}
focusTab() {
this.loadListeners();
this.blurListeners();
this.focusListeners();
for (const id in this.views.loaded) {
if (this.views.loaded.hasOwnProperty(id) && this.views.loaded[id]) {
this.views.loaded[id].style.display =
id == this.views.current.id && this.state.tabs.get(+id)
? 'flex'
: 'none';
}
}
}
communicateWithView(event) {
if (event.channel === 'enhancer:set-tab-theme') {
for (const style of event.args[0])
document.body.style.setProperty(style[0], style[1]);
}
if (
event.channel === 'enhancer:set-tab-title' &&
this.views.tabs[event.target.id]
) {
this.views.tabs[event.target.id].children[0].innerText =
event.args[0];
}
let electronWindow;
try {
@ -171,6 +149,72 @@ module.exports = (store, __exports) => {
}
});
}
newTab() {
let id = 0;
const list = new Map(this.state.tabs);
while (this.state.tabs.get(id)) id++;
list.delete(id);
if (this.views.html[id]) {
this.views.html[id].style.opacity = '0';
let unhide;
unhide = () => {
this.views.html[id].style.opacity = '';
this.views.html[id].removeEventListener('did-stop-loading', unhide);
};
this.views.html[id].addEventListener('did-stop-loading', unhide);
this.views.html[id].loadURL(this.views.current.$el().src);
}
this.openTab(id, list);
}
openTab(id, state = new Map(this.state.tabs)) {
if (!id && id !== 0) return;
this.views.current.id = id;
this.setState({ tabs: state.set(id, true) }, this.focusTab.bind(this));
}
closeTab(id) {
if ((!id && id !== 0) || !this.state.tabs.get(id)) return;
const list = new Map(this.state.tabs);
list.delete(id);
list.set(id, false);
if (![...list].filter(([id, open]) => open).length)
return electron.remote.getCurrentWindow().close();
while (
!list.get(this.views.current.id) ||
this.views.current.id === id
) {
this.views.current.id = this.views.current.id - 1;
if (this.views.current.id < 0) this.views.current.id = list.size - 1;
}
this.setState({ tabs: list }, this.focusTab.bind(this));
}
focusTab() {
this.loadListeners();
this.blurListeners();
this.focusListeners();
for (const id in this.views.loaded) {
if (this.views.loaded.hasOwnProperty(id) && this.views.loaded[id]) {
this.views.loaded[id].style.display =
id == this.views.current.id && this.state.tabs.get(+id)
? 'flex'
: 'none';
}
}
}
communicateWithView(event) {
if (event.channel === 'enhancer:set-tab-theme') {
for (const style of event.args[0])
document.body.style.setProperty(style[0], style[1]);
}
if (
event.channel === 'enhancer:set-tab-title' &&
this.views.tabs[event.target.id]
) {
this.views.tabs[event.target.id].children[0].innerText =
event.args[0];
}
}
startSearch(isPeekView) {
this.setState({
searching: true,
@ -426,8 +470,10 @@ module.exports = (store, __exports) => {
React.createElement(
'button',
{
draggable: true,
className:
'tab' + (id === this.views.current.id ? ' current' : ''),
'tab slideIn' +
(id === this.views.current.id ? ' current' : ''),
onClick: (e) => {
if (!e.target.classList.contains('close'))
this.openTab(id);

View File

@ -117,7 +117,10 @@ module.exports = async function ({ overwrite_version, friendly_errors } = {}) {
`file access forbidden - ${
process.platform === 'win32'
? 'make sure your user has elevated permissions.'
: `try running "sudo chmod a+wr -R ${err.path}"`
: `try running "sudo chmod a+wr -R ${err.path.replace(
'Notion.app',
'Notion'
)}"`
}`
);
} else if (['EIO', 'EBUSY'].includes(err.code) && friendly_errors) {

View File

@ -119,7 +119,10 @@ module.exports = async function ({
`file access forbidden - ${
process.platform === 'win32'
? 'make sure your user has elevated permissions.'
: `try running "sudo chmod a+wr -R ${err.path}"`
: `try running "sudo chmod a+wr -R ${err.path.replace(
'Notion.app',
'Notion'
)}"`
}`
);
} else if (['EIO', 'EBUSY'].includes(err.code) && friendly_errors) {