Skip to content
This repository was archived by the owner on Jun 23, 2021. It is now read-only.

Commit 27104f6

Browse files
authored
feat: add tab button to duplicate currently selected window (#104)
* feat: initial tabs adding mechanism * fix: add tab button space * fix: add tab icon
1 parent ea91121 commit 27104f6

12 files changed

Lines changed: 199 additions & 107 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
},
7676
"dependencies": {
7777
"iohook": "^0.6.5",
78-
"node-window-manager": "^2.2.0",
78+
"node-window-manager": "^2.2.1",
7979
"extract-file-icon": "0.3.2"
8080
},
8181
"iohook": {

src/main/container.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ export class Container {
3131

3232
private appWindow: AppWindow;
3333

34-
public constructor(appWindow: AppWindow, window: ProcessWindow) {
34+
public constructor(
35+
appWindow: AppWindow,
36+
window: ProcessWindow,
37+
dragged = true,
38+
) {
3539
this.appWindow = appWindow;
3640

3741
const colId = spaceId++;
@@ -50,7 +54,7 @@ export class Container {
5054

5155
window.rowId = rowId;
5256
window.columnId = colId;
53-
window.dragged = true;
57+
window.dragged = dragged;
5458

5559
this.windows.push(window);
5660

@@ -88,7 +92,7 @@ export class Container {
8892
top += row.height;
8993

9094
const window = this.windows.find(
91-
x => x.rowId === row.id && x.columnId === col.id,
95+
(x) => x.rowId === row.id && x.columnId === col.id,
9296
);
9397

9498
if (window && !window.dragged && !window.resizing) {
@@ -107,26 +111,26 @@ export class Container {
107111
}
108112

109113
public removeWindow(id: number, mouseup = true) {
110-
const win = this.windows.find(x => x.id === id);
114+
const win = this.windows.find((x) => x.id === id);
111115
if (!win) return;
112116

113-
const col = this.columns.find(x => x.id === win.columnId);
117+
const col = this.columns.find((x) => x.id === win.columnId);
114118
if (!col) return;
115119

116120
let winDetached = false;
117121

118122
if (col.rows.length === 1) {
119-
this.columns = this.columns.filter(x => x.id !== win.columnId);
123+
this.columns = this.columns.filter((x) => x.id !== win.columnId);
120124
this.detachWindow(win, mouseup);
121125
winDetached = true;
122126
}
123127

124128
if (!col) return;
125129

126-
const row = col.rows.find(x => x.id === win.rowId);
130+
const row = col.rows.find((x) => x.id === win.rowId);
127131
if (!row) return;
128132

129-
col.rows = col.rows.filter(x => x.id !== win.rowId);
133+
col.rows = col.rows.filter((x) => x.id !== win.rowId);
130134
if (!winDetached) {
131135
this.detachWindow(win, mouseup);
132136
}
@@ -147,7 +151,7 @@ export class Container {
147151

148152
window.detach(mouseup);
149153

150-
this.windows = this.windows.filter(x => x.id !== window.id);
154+
this.windows = this.windows.filter((x) => x.id !== window.id);
151155

152156
if (this.windows.length === 0) {
153157
this.appWindow.removeContainer(this);
@@ -156,15 +160,15 @@ export class Container {
156160

157161
public dragWindow(window: ProcessWindow, { x, y, type }: any) {
158162
const area = this.appWindow.getContentArea();
159-
const win = this.windows.find(x => x.id === window.id);
163+
const win = this.windows.find((x) => x.id === window.id);
160164

161165
if (win) {
162166
if (win.resizing) return;
163167

164-
const col = this.columns.find(x => x.id === win.columnId);
168+
const col = this.columns.find((x) => x.id === win.columnId);
165169
if (!col) return;
166170

167-
const row = col.rows.find(x => x.id === win.rowId);
171+
const row = col.rows.find((x) => x.id === win.rowId);
168172

169173
win.dragged = true;
170174

@@ -261,10 +265,10 @@ export class Container {
261265
}
262266

263267
public resizeWindow(window: ProcessWindow, resizeWinCb: () => void) {
264-
const win = this.windows.find(x => x.id === window.id);
268+
const win = this.windows.find((x) => x.id === window.id);
265269
if (!win) return;
266270

267-
const col = this.columns.find(x => x.id === win.columnId);
271+
const col = this.columns.find((x) => x.id === win.columnId);
268272
if (!col) return;
269273

270274
const winBounds = win.getBounds();
@@ -290,7 +294,7 @@ export class Container {
290294
c.weight = c.width / baseWidth;
291295
}
292296
} else if (winBounds.height !== win.lastBounds.height) {
293-
const row = col.rows.find(x => x.id === win.rowId);
297+
const row = col.rows.find((x) => x.id === win.rowId);
294298
if (!row) return;
295299

296300
const index = winBounds.y !== win.lastBounds.y ? -1 : 1;

src/main/windows/app.ts

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ export class AppWindow extends BrowserWindow {
135135
this.selectContainer(this.containers.find((x) => x.id === id));
136136
});
137137

138+
ipcMain.on('new-tab', () => {
139+
this.newTab();
140+
});
141+
138142
ipcMain.on('detach-window', (e, id: number) => {
139143
for (const container of this.containers) {
140144
container.removeWindow(id, true);
@@ -241,29 +245,7 @@ export class AppWindow extends BrowserWindow {
241245
this.draggedWindow.resizing = false;
242246

243247
if (this.willAttachWindow) {
244-
const win = this.draggedWindow;
245-
const container = this.draggedContainer;
246-
247-
if (platform() === 'win32') {
248-
const handle = this.getNativeWindowHandle().readInt32LE(0);
249-
win.setOwner(handle);
250-
}
251-
252-
if (platform() === 'darwin' && this.containers.length === 0) {
253-
this.setBounds({ height: TOOLBAR_HEIGHT } as any);
254-
this.setMaximumSize(0, TOOLBAR_HEIGHT);
255-
}
256-
257-
this.containers.push(this.draggedContainer);
258-
this.willAttachWindow = false;
259-
260-
this.draggedContainer.rearrangeWindows();
261-
262-
setTimeout(() => {
263-
this.selectContainer(container);
264-
}, 50);
265-
266-
this.controlShortcuts(win.id);
248+
this.attachNewContainer(this.draggedContainer, this.draggedWindow);
267249
} else if (this.willSplitWindow && !this.detached) {
268250
this.willSplitWindow = false;
269251

@@ -420,34 +402,9 @@ export class AppWindow extends BrowserWindow {
420402
this.selectedContainer.removeWindow(win.id, e.type === 'mouseup');
421403
}
422404

423-
const container = new Container(this, win);
424-
425-
const title = this.draggedWindow.getTitle();
405+
const container = this.prepareContainer(this.draggedWindow);
426406

427407
this.draggedContainer = container;
428-
win.lastTitle = title;
429-
430-
const icon = fileIcon(win.path, 16);
431-
432-
this.webContents.send('add-tab', {
433-
id: container.id,
434-
title,
435-
icon,
436-
});
437-
438-
try {
439-
Vibrant.from(icon)
440-
.getPalette()
441-
.then((palette) => {
442-
this.webContents.send(
443-
'tab-background',
444-
container.id,
445-
palette.Vibrant.hex,
446-
);
447-
});
448-
} catch (e) {
449-
console.error(e);
450-
}
451408

452409
this.draggedIn = true;
453410
this.willAttachWindow = true;
@@ -461,6 +418,92 @@ export class AppWindow extends BrowserWindow {
461418
}
462419
}
463420

421+
public prepareContainer(window: ProcessWindow, dragged = true) {
422+
const container = new Container(this, window, dragged);
423+
424+
const title = window.getTitle();
425+
const icon = fileIcon(window.path, 16);
426+
427+
window.lastTitle = title;
428+
429+
this.webContents.send('add-tab', {
430+
id: container.id,
431+
title,
432+
icon,
433+
});
434+
435+
try {
436+
Vibrant.from(icon)
437+
.getPalette()
438+
.then((palette) => {
439+
this.webContents.send(
440+
'tab-background',
441+
container.id,
442+
palette.Vibrant.hex,
443+
);
444+
});
445+
} catch (e) {
446+
console.error(e);
447+
}
448+
449+
return container;
450+
}
451+
452+
public attachNewContainer(container: Container, win: ProcessWindow) {
453+
if (platform() === 'win32') {
454+
const handle = this.getNativeWindowHandle().readInt32LE(0);
455+
win.setOwner(handle);
456+
}
457+
458+
if (platform() === 'darwin' && this.containers.length === 0) {
459+
this.setBounds({ height: TOOLBAR_HEIGHT } as any);
460+
this.setMaximumSize(0, TOOLBAR_HEIGHT);
461+
}
462+
463+
this.containers.push(container);
464+
this.willAttachWindow = false;
465+
466+
container.rearrangeWindows();
467+
468+
setTimeout(() => {
469+
this.selectContainer(container);
470+
}, 50);
471+
472+
this.controlShortcuts(win.id);
473+
}
474+
475+
public async newTab() {
476+
if (this.selectedContainer?.windows?.length === 1) {
477+
const window = await this.createNewWindow(
478+
this.selectedContainer.windows[0].path,
479+
);
480+
481+
this.addTabWithWindow(window);
482+
}
483+
}
484+
485+
public createNewWindow(path: string): Promise<ProcessWindow> {
486+
return new Promise((resolve, reject) => {
487+
windowManager.createProcess(path);
488+
489+
const timeout = setTimeout(() => {
490+
reject(new Error('Creating new window timed out'));
491+
}, 5000);
492+
493+
windowManager.once('window-activated', (win: ProcessWindow) => {
494+
if (win.path === path) {
495+
clearTimeout(timeout);
496+
resolve(new ProcessWindow(win.id, this));
497+
}
498+
});
499+
});
500+
}
501+
502+
public addTabWithWindow(win: ProcessWindow) {
503+
const container = this.prepareContainer(win, false);
504+
this.attachNewContainer(container, win);
505+
}
506+
464507
public getContentArea() {
465508
const bounds = this.getContentBounds();
466509

src/renderer/views/app/components/Tabbar/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@ import * as React from 'react';
33

44
import HorizontalScrollbar from '../HorizontalScrollbar';
55
import store from '~/renderer/views/app/store';
6-
import { StyledTabbar, TabsContainer } from './style';
6+
import { StyledTabbar, TabsContainer, AddTab } from './style';
77
import { Tabs } from '../Tabs';
8+
import { icons } from '../../constants';
89

910
const getContainer = () => store.tabsStore.containerRef.current;
1011

1112
const onMouseEnter = () => (store.tabsStore.scrollbarVisible = true);
1213

1314
const onMouseLeave = () => (store.tabsStore.scrollbarVisible = false);
1415

16+
const onAddTabClick = () => {
17+
store.tabsStore.newTab();
18+
};
19+
1520
export const Tabbar = observer(() => {
1621
return (
1722
<StyledTabbar>
@@ -22,6 +27,11 @@ export const Tabbar = observer(() => {
2227
>
2328
<Tabs />
2429
</TabsContainer>
30+
<AddTab
31+
icon={icons.add}
32+
onClick={onAddTabClick}
33+
divRef={(r: any) => (store.addTab.ref = r)}
34+
/>
2535
<HorizontalScrollbar
2636
ref={store.tabsStore.scrollbarRef}
2737
enabled={store.tabsStore.scrollable}
Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
import styled from 'styled-components';
2-
3-
export const StyledTabbar = styled.div`
4-
height: 100%;
5-
width: 100%;
6-
position: relative;
7-
overflow: hidden;
8-
transition: 0.3s opacity, 0.3s transform;
9-
margin-right: 32px;
10-
display: flex;
11-
`;
12-
13-
export const TabsContainer = styled.div`
14-
height: 100%;
15-
width: 100%;
16-
position: relative;
17-
overflow: hidden;
18-
white-space: nowrap;
19-
display: flex;
20-
align-items: center;
21-
`;
1+
import styled from 'styled-components';
2+
import { ToolbarButton } from '../ToolbarButton';
3+
import { TOOLBAR_BUTTON_WIDTH } from '../../constants';
4+
5+
export const StyledTabbar = styled.div`
6+
height: 100%;
7+
width: 100%;
8+
position: relative;
9+
overflow: hidden;
10+
transition: 0.3s opacity, 0.3s transform;
11+
margin-right: 32px;
12+
display: flex;
13+
`;
14+
15+
export const TabsContainer = styled.div`
16+
height: 100%;
17+
width: calc(100% - ${TOOLBAR_BUTTON_WIDTH}px);
18+
position: relative;
19+
overflow: hidden;
20+
white-space: nowrap;
21+
display: flex;
22+
align-items: center;
23+
`;
24+
25+
export const AddTab = styled(ToolbarButton)`
26+
position: absolute;
27+
left: 0;
28+
`;
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
export const icons = {
2-
close: require('~/shared/resources/icons/close.svg'),
3-
dropWindow: require('~/shared/resources/icons/drop-window.svg'),
4-
download: require('~/shared/resources/icons/download.svg'),
5-
theme: require('~/shared/resources/icons/theme.svg'),
6-
more: require('~/shared/resources/icons/more.svg'),
7-
photo: require('~/shared/resources/icons/photo.svg'),
8-
};
1+
export const icons = {
2+
close: require('~/shared/resources/icons/close.svg'),
3+
add: require('~/shared/resources/icons/add.svg'),
4+
dropWindow: require('~/shared/resources/icons/drop-window.svg'),
5+
download: require('~/shared/resources/icons/download.svg'),
6+
theme: require('~/shared/resources/icons/theme.svg'),
7+
more: require('~/shared/resources/icons/more.svg'),
8+
photo: require('~/shared/resources/icons/photo.svg'),
9+
};

0 commit comments

Comments
 (0)