Skip to content

Commit 43e1e1c

Browse files
committed
Refactor DeviceManager:
- Moved the DeviceManager logic to a separate DeviceManager.tsx file - Updated imports - Refactored code as per the PR review into separate DeviceManagerLabel.tsx file - Ensured AppContent now correctly references the DeviceManager
1 parent f11a767 commit 43e1e1c

File tree

4 files changed

+289
-270
lines changed

4 files changed

+289
-270
lines changed

desktop-app/src/renderer/AppContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import './App.css';
88
import ThemeProvider from './context/ThemeProvider';
99
import type { AppView } from './store/features/ui';
1010
import { APP_VIEWS, selectAppView } from './store/features/ui';
11-
import DeviceManager from './components/DeviceManager';
11+
import { DeviceManager } from './components/DeviceManager';
1212
import KeyboardShortcutsManager from './components/KeyboardShortcutsManager';
1313
import { ReleaseNotes } from './components/ReleaseNotes';
1414
import { Sponsorship } from './components/Sponsorship';
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { useEffect, useState } from 'react';
2+
import { Icon } from '@iconify/react';
3+
import { useDispatch, useSelector } from 'react-redux';
4+
import { DndProvider } from 'react-dnd';
5+
import { HTML5Backend } from 'react-dnd-html5-backend';
6+
import {
7+
selectActiveSuite,
8+
setDevices,
9+
setSuiteDevices,
10+
} from 'renderer/store/features/device-manager';
11+
import { APP_VIEWS, setAppView } from 'renderer/store/features/ui';
12+
import { defaultDevices, Device, getDevicesMap } from 'common/deviceList';
13+
14+
import cx from 'classnames';
15+
import Button from '../Button';
16+
import DeviceLabel from './DeviceLabel';
17+
import DeviceDetailsModal from './DeviceDetailsModal';
18+
import { PreviewSuites } from './PreviewSuites';
19+
import { ManageSuitesTool } from './PreviewSuites/ManageSuitesTool/ManageSuitesTool';
20+
import { Divider } from '../Divider';
21+
import { AccordionItem, Accordion } from '../Accordion';
22+
import { DropDown } from '../DropDown';
23+
import DeviceManagerLabel from './DeviceManagerLabel';
24+
25+
const filterDevices = (devices: Device[], filter: string) => {
26+
const sanitizedFilter = filter.trim().toLowerCase();
27+
28+
return devices.filter((device: Device) =>
29+
`${device.name.toLowerCase()}${device.width}x${device.height}`.includes(
30+
sanitizedFilter
31+
)
32+
);
33+
};
34+
35+
const DeviceManager = () => {
36+
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState<boolean>(false);
37+
const [selectedDevice, setSelectedDevice] = useState<Device | undefined>(
38+
undefined
39+
);
40+
const dispatch = useDispatch();
41+
const activeSuite = useSelector(selectActiveSuite);
42+
const devices = activeSuite.devices?.map((id) => getDevicesMap()[id]);
43+
const [searchText, setSearchText] = useState<string>('');
44+
const [filteredType, setFilteredType] = useState<string | null>(null);
45+
const [filteredDevices, setFilteredDevices] =
46+
useState<Device[]>(defaultDevices);
47+
const [customDevices, setCustomDevices] = useState<Device[]>(
48+
window.electron.store.get('deviceManager.customDevices')
49+
);
50+
const [filteredCustomDevices, setFilteredCustomDevices] =
51+
useState<Device[]>(customDevices);
52+
const deviceTypes = Array.from(
53+
new Set(defaultDevices.map((device) => device.type))
54+
);
55+
56+
useEffect(() => {
57+
const filtered = filterDevices(defaultDevices, searchText).filter(
58+
(device) => (filteredType ? device.type === filteredType : true)
59+
);
60+
const filteredCustom = filterDevices(customDevices, searchText).filter(
61+
(device) => (filteredType ? device.type === filteredType : true)
62+
);
63+
setFilteredDevices(filtered);
64+
setFilteredCustomDevices(filteredCustom);
65+
}, [customDevices, searchText, filteredType]);
66+
67+
const saveCustomDevices = (newCustomDevices: Device[]) => {
68+
setCustomDevices(newCustomDevices);
69+
window.electron.store.set('deviceManager.customDevices', newCustomDevices);
70+
setFilteredCustomDevices(filterDevices(newCustomDevices, searchText));
71+
};
72+
73+
const onSaveDevice = async (device: Device, isNew: boolean) => {
74+
const newCustomDevices = isNew
75+
? [...customDevices, device]
76+
: customDevices.map((d) => (d.id === device.id ? device : d));
77+
saveCustomDevices(newCustomDevices);
78+
if (isNew) {
79+
dispatch(
80+
setSuiteDevices({
81+
suite: activeSuite.id,
82+
devices: [...activeSuite.devices, device.id],
83+
})
84+
);
85+
}
86+
};
87+
88+
const onRemoveDevice = (device: Device) => {
89+
const newCustomDevices = customDevices.filter((d) => d.id !== device.id);
90+
saveCustomDevices(newCustomDevices);
91+
dispatch(
92+
setSuiteDevices({
93+
suite: activeSuite.id,
94+
devices: activeSuite.devices.filter((d) => d !== device.id),
95+
})
96+
);
97+
};
98+
99+
const onShowDeviceDetails = (device: Device) => {
100+
setSelectedDevice(device);
101+
setIsDetailsModalOpen(true);
102+
};
103+
104+
return (
105+
<div className="mx-auto flex w-4/5 flex-col gap-4 rounded-lg p-8">
106+
<div className="flex w-full justify-end text-3xl">
107+
<Button onClick={() => dispatch(setAppView(APP_VIEWS.BROWSER))}>
108+
<Icon icon="ic:round-close" fontSize={18} />
109+
</Button>
110+
</div>
111+
<div>
112+
<div className="flex items-center justify-end justify-between ">
113+
<h2 className="text-2xl font-bold">Device Manager</h2>
114+
<ManageSuitesTool setCustomDevicesState={setCustomDevices} />
115+
</div>
116+
<Divider />
117+
<Accordion>
118+
<AccordionItem title="MANAGE SUITES">
119+
<PreviewSuites />
120+
</AccordionItem>
121+
</Accordion>
122+
<Divider />
123+
<div className="my-4 flex items-start justify-end justify-between">
124+
<div className="flex w-fit flex-col items-start px-1">
125+
<h2 className="text-2xl font-bold">Manage Devices</h2>
126+
</div>
127+
<div>
128+
<DropDown
129+
label={
130+
<div className="flex items-center gap-2">
131+
<Icon icon="mdi:devices" fontSize={18} />
132+
<span>Device Type</span>
133+
</div>
134+
}
135+
options={[
136+
{
137+
label: (
138+
<DeviceManagerLabel
139+
type={null}
140+
filteredType={filteredType}
141+
label="All Device Types"
142+
/>
143+
),
144+
onClick: () => setFilteredType(null),
145+
},
146+
...deviceTypes.map((type) => ({
147+
label: (
148+
<DeviceManagerLabel
149+
type={type}
150+
filteredType={filteredType}
151+
label={type}
152+
/>
153+
),
154+
onClick: () => setFilteredType(type),
155+
})),
156+
]}
157+
/>
158+
</div>
159+
<div className="flex w-fit items-center bg-white px-1 dark:bg-slate-900">
160+
<Icon icon="ic:outline-search" height={24} />
161+
<input
162+
className="w-60 rounded bg-inherit px-2 py-1 focus:outline-none"
163+
placeholder="Search ..."
164+
value={searchText}
165+
onChange={(e) => setSearchText(e.target.value)}
166+
/>
167+
</div>
168+
</div>
169+
<Accordion>
170+
<>
171+
<AccordionItem title="DEFAULT DEVICES">
172+
<div className="ml-4 flex flex-row flex-wrap gap-4">
173+
{filteredDevices.map((device) => (
174+
<DeviceLabel
175+
device={device}
176+
key={device.id}
177+
onShowDeviceDetails={onShowDeviceDetails}
178+
disableSelectionControls={
179+
devices.find((d) => d.id === device.id) != null &&
180+
devices.length === 1
181+
}
182+
/>
183+
))}
184+
{filteredDevices.length === 0 ? (
185+
<div className="m-10 flex w-full items-center justify-center">
186+
Sorry, no matching devices found.
187+
<Icon icon="mdi:emoticon-sad-outline" className="ml-1" />
188+
</div>
189+
) : null}
190+
</div>
191+
</AccordionItem>
192+
<AccordionItem title="CUSTOM DEVICES">
193+
<div className="ml-4 flex flex-row flex-wrap gap-4">
194+
{filteredCustomDevices.map((device) => (
195+
<DeviceLabel
196+
device={device}
197+
key={device.id}
198+
onShowDeviceDetails={onShowDeviceDetails}
199+
/>
200+
))}
201+
{customDevices.length === 0 ? (
202+
<div className="m-10 flex w-full flex-col items-center justify-center">
203+
<span>No custom devices added yet!</span>
204+
<Button
205+
className="m-4 rounded-l"
206+
onClick={() => setIsDetailsModalOpen(true)}
207+
isActive
208+
>
209+
<Icon icon="ic:baseline-add" />
210+
<span className="pr-2 pl-2">Add Custom Device</span>
211+
</Button>
212+
</div>
213+
) : null}
214+
{customDevices.length > 0 &&
215+
filteredCustomDevices.length === 0 ? (
216+
<div className="m-10 flex w-full items-center justify-center">
217+
Sorry, no matching devices found.
218+
<Icon icon="mdi:emoticon-sad-outline" className="ml-1" />
219+
</div>
220+
) : null}
221+
<Button
222+
className={
223+
customDevices.length < 1 || filteredCustomDevices.length < 1
224+
? 'hidden'
225+
: 'rounded-l'
226+
}
227+
onClick={() => setIsDetailsModalOpen(true)}
228+
isActive
229+
>
230+
<Icon icon="ic:baseline-add" />
231+
<span className="pr-2 pl-2">Add Custom Device</span>
232+
</Button>
233+
</div>
234+
</AccordionItem>
235+
</>
236+
</Accordion>
237+
</div>
238+
<DeviceDetailsModal
239+
onSaveDevice={onSaveDevice}
240+
existingDevices={[...defaultDevices, ...customDevices]}
241+
isCustom
242+
isOpen={isDetailsModalOpen}
243+
onClose={() => {
244+
setSelectedDevice(undefined);
245+
setIsDetailsModalOpen(false);
246+
}}
247+
device={selectedDevice}
248+
onRemoveDevice={onRemoveDevice}
249+
/>
250+
</div>
251+
);
252+
};
253+
254+
export default () => (
255+
<DndProvider backend={HTML5Backend}>
256+
<DeviceManager />
257+
</DndProvider>
258+
);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Icon } from '@iconify/react';
2+
import cx from 'classnames';
3+
4+
type DeviceManagerLabelProps = {
5+
filteredType: string | null;
6+
type: string | null;
7+
label: string;
8+
};
9+
10+
const DeviceManagerLabel = ({
11+
filteredType,
12+
type,
13+
label,
14+
}: DeviceManagerLabelProps) => {
15+
return (
16+
<div className="flex items-center gap-2">
17+
{filteredType === type && <Icon icon="mdi:check" />}
18+
<span
19+
className={cx('capitalize', {
20+
'font-semibold text-black dark:text-white': filteredType === type,
21+
})}
22+
>
23+
{label}
24+
</span>
25+
</div>
26+
);
27+
};
28+
29+
export default DeviceManagerLabel;

0 commit comments

Comments
 (0)