Skip to content

Commit 442a3be

Browse files
committed
feat: add multi-window merge + make selection UI nicer
1 parent 64e8f94 commit 442a3be

File tree

3 files changed

+113
-30
lines changed

3 files changed

+113
-30
lines changed

src/App.css

+43-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@
1111
}
1212

1313
.window-item {
14-
border: 1px solid #ccc;
15-
margin-bottom: 10px;
16-
padding: 5px;
14+
display: flex;
15+
align-items: center;
16+
padding: 10px;
17+
cursor: pointer;
18+
border-bottom: 1px solid #ccc;
19+
}
20+
21+
.window-item.selected { /* {{ edit: Style for selected window-item }} */
22+
background-color: #e0f7fa;
1723
}
1824

1925
.window-label {
@@ -41,3 +47,37 @@
4147
flex-grow: 1;
4248
}
4349

50+
.active-tab {
51+
display: flex;
52+
align-items: center;
53+
margin-left: auto;
54+
}
55+
56+
.tab-dropdown { /* {{ edit: Make dropdown scrollable when overflow }} */
57+
max-height: 200px;
58+
overflow-y: auto;
59+
position: absolute;
60+
background-color: white;
61+
border: 1px solid #ccc;
62+
list-style: none;
63+
padding: 5px;
64+
margin-top: 5px;
65+
z-index: 1000; /* {{ edit: Ensure dropdown appears above other elements }} */
66+
}
67+
68+
.tab-dropdown .tab-item {
69+
padding: 5px 10px;
70+
}
71+
72+
/* Adjust the merge button for better usability */
73+
.merge-button {
74+
padding: 10px 20px;
75+
font-size: 16px;
76+
cursor: pointer;
77+
}
78+
79+
.merge-button:disabled { /* {{ edit: Style disabled merge button }} */
80+
opacity: 0.5;
81+
cursor: not-allowed;
82+
}
83+

src/App.jsx

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// App.jsx
2-
import React, { useEffect, useState, useCallback } from 'react';
2+
import { useEffect, useState, useCallback } from 'react';
33
import WindowItem from './WindowItem';
44
import {
55
getAllWindows,
@@ -36,22 +36,22 @@ function App() {
3636

3737
// Merge the selected windows
3838
const mergeWindows = async () => {
39-
if (selectedWindowIds.length !== 2) {
40-
alert('Please select exactly two windows to merge.');
39+
if (selectedWindowIds.length < 2) { // {{ edit: Allow merging two or more windows }}
40+
alert('Please select two or more windows to merge.');
4141
return;
4242
}
4343

44-
const [windowId1, windowId2] = selectedWindowIds.map(Number);
44+
const [targetWindowId, ...otherWindowIds] = selectedWindowIds.map(Number);
4545

46-
// Get all tabs from the second window
47-
const tabs = await queryTabs({ windowId: windowId2 });
48-
const tabIds = tabs.map((tab) => tab.id);
46+
// Get all tabs from the other windows
47+
const allOtherTabs = await Promise.all(otherWindowIds.map(id => queryTabs({ windowId: id })));
48+
const tabIdsToMove = allOtherTabs.flat().map(tab => tab.id);
4949

50-
// Move tabs to the first window
51-
await moveTabs(tabIds, { windowId: windowId1, index: -1 });
50+
// Move tabs to the target window
51+
await moveTabs(tabIdsToMove, { windowId: targetWindowId, index: -1 });
5252

53-
// Close the second window
54-
await removeWindow(windowId2);
53+
// Close the other windows
54+
await Promise.all(otherWindowIds.map(id => removeWindow(id)));
5555

5656
// Refresh the window list
5757
await fetchWindows();
@@ -69,7 +69,11 @@ function App() {
6969
onSelect={handleWindowSelect}
7070
/>
7171
))}
72-
<button onClick={mergeWindows} className="merge-button">
72+
<button
73+
onClick={mergeWindows}
74+
className="merge-button"
75+
disabled={selectedWindowIds.length < 2} // {{ edit: Disable button if fewer than two windows are selected }}
76+
>
7377
Merge Selected Windows
7478
</button>
7579
</div>

src/WindowItem.jsx

+54-15
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,64 @@
11
// WindowItem.jsx
2-
import React from 'react';
2+
import React, { useState, useRef, useEffect } from 'react'; // {{ edit: Import useRef and useEffect }}
3+
import PropTypes from 'prop-types'; // {{ edit: Import PropTypes for props validation }}
34
import TabItem from './TabItem';
45

56
function WindowItem({ window, isSelected, onSelect }) {
7+
const [dropdownOpen, setDropdownOpen] = useState(false);
8+
const dropdownRef = useRef(null); // {{ edit: Create ref for dropdown }}
9+
const activeTab = window.tabs.find(tab => tab.active);
10+
11+
// {{ edit: Handle click outside to close dropdown }}
12+
useEffect(() => {
13+
const handleClickOutside = (event) => {
14+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
15+
setDropdownOpen(false);
16+
}
17+
};
18+
19+
if (dropdownOpen) {
20+
document.addEventListener('mousedown', handleClickOutside);
21+
} else {
22+
document.removeEventListener('mousedown', handleClickOutside);
23+
}
24+
25+
return () => {
26+
document.removeEventListener('mousedown', handleClickOutside);
27+
};
28+
}, [dropdownOpen]);
29+
630
return (
7-
<div className="window-item">
8-
<label>
9-
<input
10-
type="checkbox"
11-
checked={isSelected}
12-
onChange={() => onSelect(window.id.toString())}
13-
/>
14-
<span className="window-label"> Window {window.id}</span>
15-
</label>
16-
<ul className="tab-list">
17-
{window.tabs.map((tab) => (
18-
<TabItem key={tab.id} tab={tab} />
19-
))}
20-
</ul>
31+
<div
32+
className={`window-item ${isSelected ? 'selected' : ''}`} // {{ edit: Add 'selected' class based on isSelected prop }}
33+
onClick={() => onSelect(window.id.toString())}
34+
>
35+
<span className="window-label">{activeTab.title}</span>
36+
{activeTab && (
37+
<div className="active-tab" onClick={(e) => e.stopPropagation()} ref={dropdownRef}>
38+
<TabItem tab={activeTab} />
39+
<button onClick={() => setDropdownOpen(!dropdownOpen)}>
40+
{dropdownOpen ? '▲' : '▼'}
41+
</button>
42+
{dropdownOpen && (
43+
<ul className="tab-dropdown">
44+
{window.tabs.map(tab => ( // {{ edit: Include all tabs in dropdown }}
45+
<TabItem key={tab.id} tab={tab} />
46+
))}
47+
</ul>
48+
)}
49+
</div>
50+
)}
2151
</div>
2252
);
2353
}
2454

55+
WindowItem.propTypes = { // {{ edit: Define prop types for WindowItem }}
56+
window: PropTypes.shape({
57+
id: PropTypes.number.isRequired,
58+
tabs: PropTypes.arrayOf(PropTypes.object).isRequired,
59+
}).isRequired,
60+
isSelected: PropTypes.bool.isRequired,
61+
onSelect: PropTypes.func.isRequired,
62+
};
63+
2564
export default WindowItem;

0 commit comments

Comments
 (0)