Skip to content

Commit 44f0a76

Browse files
authored
Merge pull request responsively-org#765 from responsively-org/v1-device-manager-improvements
v1: Device Manager - Add, edit and delete custom devices
2 parents 40d99bb + 11fef41 commit 44f0a76

File tree

13 files changed

+549
-30
lines changed

13 files changed

+549
-30
lines changed

desktop-app/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
},
103103
"dependencies": {
104104
"@fontsource/lato": "^4.5.8",
105+
"@headlessui/react": "^1.7.4",
105106
"@iconify/react": "^3.2.2",
106107
"@reduxjs/toolkit": "^1.8.3",
107108
"autoprefixer": "^10.4.7",
@@ -277,7 +278,8 @@
277278
"electronmon": {
278279
"patterns": [
279280
"!**/**",
280-
"src/main/**/*"
281+
"src/main/**/*",
282+
"src/store/**/*"
281283
],
282284
"logLevel": "quiet"
283285
}

desktop-app/src/common/deviceList.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface Device {
88
isTouchCapable: boolean;
99
isMobileCapable: boolean;
1010
capabilities: string[];
11+
isCustom?: boolean;
1112
}
1213

1314
const devices: Device[] = [
@@ -645,13 +646,16 @@ const devices: Device[] = [
645646
isMobileCapable: true,
646647
},
647648
];
649+
650+
const customDevices: Device[] = window.electron.store.get(
651+
'deviceManager.customDevices'
652+
);
648653
type DeviceMap = { [key: string]: Device };
649-
export const defaultDevicesMap: DeviceMap = devices.reduce(
650-
(map: DeviceMap, device) => {
654+
export const getDevicesMap = (): DeviceMap => {
655+
return [...devices, ...customDevices].reduce((map: DeviceMap, device) => {
651656
map[device.name] = device;
652657
return map;
653-
},
654-
{}
655-
);
658+
}, {});
659+
};
656660

657661
export default devices;

desktop-app/src/renderer/components/Button/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const Button = ({
4646
return (
4747
<button
4848
className={cx(
49+
{ [className]: className?.length },
4950
`flex items-center justify-center rounded-sm p-1 ${
5051
disableHoverEffects === false ? `${hoverBg} ${hoverBgDark}` : ''
5152
} focus:outline-none`,
@@ -55,7 +56,6 @@ const Button = ({
5556
'bg-slate-200': isActionButton,
5657
'dark:bg-slate-700': isActionButton,
5758
'px-2': isActionButton,
58-
[className]: className?.length,
5959
}
6060
)}
6161
type="button"
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import { Device } from 'common/deviceList';
2+
import { useEffect, useState } from 'react';
3+
import Button from '../Button';
4+
import Input from '../Input';
5+
import Modal from '../Modal';
6+
import Select from '../Select';
7+
8+
interface Props {
9+
device?: Device;
10+
onAddDevice: (device: Device, isNew: boolean) => Promise<void>;
11+
onRemoveDevice: (device: Device) => void;
12+
existingDevices: Device[];
13+
isCustom: boolean;
14+
isOpen: boolean;
15+
onClose: () => void;
16+
}
17+
18+
const DeviceDetailsModal = ({
19+
onAddDevice,
20+
onRemoveDevice,
21+
existingDevices,
22+
device,
23+
isOpen,
24+
onClose,
25+
}: Props) => {
26+
const [name, setName] = useState<string>(device?.name ?? '');
27+
const [width, setWidth] = useState<number>(device?.width ?? 400);
28+
const [height, setHeight] = useState<number>(device?.height ?? 600);
29+
const [userAgent, setUserAgent] = useState<string>(device?.userAgent ?? '');
30+
const [type, setType] = useState<string>(device?.type ?? 'phone');
31+
const [dpi, setDpi] = useState<number>(device?.dpi ?? 1);
32+
const [isTouchCapable, setIsTouchCapable] = useState<boolean>(
33+
device?.isTouchCapable ?? true
34+
);
35+
const [isMobileCapable, setIsMobileCapable] = useState<boolean>(
36+
device?.isMobileCapable ?? true
37+
);
38+
39+
useEffect(() => {
40+
if (device) {
41+
setName(device.name);
42+
setWidth(device.width);
43+
setHeight(device.height);
44+
setUserAgent(device.userAgent);
45+
setType(device.type);
46+
setDpi(device.dpi);
47+
setIsTouchCapable(device.isTouchCapable);
48+
setIsMobileCapable(device.isMobileCapable);
49+
} else {
50+
setName('');
51+
setWidth(400);
52+
setHeight(600);
53+
setUserAgent('');
54+
setType('phone');
55+
setDpi(1);
56+
setIsTouchCapable(true);
57+
setIsMobileCapable(true);
58+
}
59+
}, [device]);
60+
61+
useEffect(() => {
62+
if (type === 'phone' || type === 'tablet') {
63+
setUserAgent(
64+
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
65+
);
66+
setIsMobileCapable(true);
67+
setIsTouchCapable(true);
68+
} else {
69+
setUserAgent(
70+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
71+
);
72+
setIsMobileCapable(false);
73+
setIsTouchCapable(false);
74+
}
75+
}, [type, userAgent]);
76+
77+
const isNew = !device;
78+
const isCustom = device != null ? device.isCustom ?? false : true;
79+
80+
const handleAddDevice = async (): Promise<void> => {
81+
if (device == null && existingDevices.find((d) => d.name === name)) {
82+
// eslint-disable-next-line no-alert
83+
return alert(
84+
'Device With the name already exists, try with a different name'
85+
);
86+
}
87+
const capabilities = [];
88+
if (isTouchCapable) {
89+
capabilities.push('touch');
90+
}
91+
if (isMobileCapable) {
92+
capabilities.push('mobile');
93+
}
94+
await onAddDevice(
95+
{
96+
name,
97+
width,
98+
height,
99+
userAgent,
100+
type,
101+
dpi,
102+
isTouchCapable,
103+
isMobileCapable,
104+
capabilities,
105+
isCustom,
106+
},
107+
device == null
108+
);
109+
110+
return onClose();
111+
};
112+
113+
return (
114+
<>
115+
<Modal
116+
isOpen={isOpen}
117+
onClose={onClose}
118+
title={isNew ? 'Add Custom Device' : 'Device Details'}
119+
>
120+
<div className="flex flex-col gap-4">
121+
<div className="flex w-[420px] flex-col gap-2">
122+
<Input
123+
label="Device Name"
124+
type="text"
125+
placeholder="My Mobile Device"
126+
value={name}
127+
onChange={(e) => setName(e.target.value)}
128+
disabled={!isCustom || !isNew}
129+
/>
130+
{!isNew && isCustom ? (
131+
<p>Note: Device name cannot be modified once created!</p>
132+
) : null}
133+
<Input
134+
label="Device Width"
135+
type="number"
136+
placeholder="1200"
137+
min="100"
138+
value={width}
139+
onChange={(e) => setWidth(parseInt(e.target.value, 10))}
140+
disabled={!isCustom}
141+
/>
142+
<Input
143+
label="Device Height"
144+
type="number"
145+
placeholder="800"
146+
min="100"
147+
value={height}
148+
onChange={(e) => setHeight(parseInt(e.target.value, 10))}
149+
disabled={!isCustom}
150+
/>
151+
<Input
152+
label="Device DPI"
153+
type="number"
154+
min="1"
155+
value={dpi}
156+
onChange={(e) => setDpi(parseInt(e.target.value, 10))}
157+
disabled={!isCustom}
158+
/>
159+
<Select
160+
label="Device type"
161+
onChange={(e) => setType(e.target.value)}
162+
value={type}
163+
disabled={!isCustom}
164+
>
165+
<option value="notebook">Desktop</option>
166+
<option value="phone">Phone</option>
167+
<option value="tablet">Tablet</option>
168+
</Select>
169+
<Input
170+
label="User Agent String"
171+
type="text"
172+
placeholder="User agent string for this device's network requests"
173+
value={userAgent}
174+
onChange={(e) => setUserAgent(e.target.value)}
175+
disabled={!isCustom}
176+
/>
177+
<Input
178+
label="Touch Capable"
179+
type="checkbox"
180+
checked={isTouchCapable}
181+
onChange={(e) => setIsTouchCapable(e.target.checked)}
182+
disabled={!isCustom}
183+
/>
184+
<Input
185+
label="Mobile Capable (Rotatable)"
186+
type="checkbox"
187+
checked={isMobileCapable}
188+
onChange={(e) => setIsMobileCapable(e.target.checked)}
189+
disabled={!isCustom}
190+
/>
191+
</div>
192+
{isCustom ? (
193+
<div className="flex flex-row justify-between">
194+
{device != null ? (
195+
<Button
196+
className="bg-red-500 px-2 text-white hover:bg-red-700 dark:hover:bg-red-600"
197+
onClick={async () => {
198+
await onRemoveDevice(device);
199+
onClose();
200+
}}
201+
>
202+
Delete
203+
</Button>
204+
) : (
205+
<div />
206+
)}
207+
208+
<div className="flex flex-row justify-end gap-2">
209+
<Button className="px-2" onClick={onClose}>
210+
Cancel
211+
</Button>
212+
<Button className="px-2" onClick={handleAddDevice} isActive>
213+
{isNew ? 'Add' : 'Save'}
214+
</Button>
215+
</div>
216+
</div>
217+
) : (
218+
<div className="flex flex-row justify-end gap-2">
219+
<Button className="px-2" onClick={onClose}>
220+
Close
221+
</Button>
222+
</div>
223+
)}
224+
</div>
225+
</Modal>
226+
</>
227+
);
228+
};
229+
230+
export default DeviceDetailsModal;

desktop-app/src/renderer/components/DeviceManager/DeviceLabel.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ import {
66
selectDevices,
77
setDevices,
88
} from 'renderer/store/features/device-manager';
9+
import Button from '../Button';
910

1011
export const DND_TYPE = 'Device';
1112

1213
interface Props {
1314
device: Device;
1415
enableDnd?: boolean;
1516
moveDevice?: (device: Device, atIndex: number) => void;
17+
onShowDeviceDetails: (device: Device) => void;
1618
}
1719

1820
const DeviceLabel = ({
1921
device,
2022
moveDevice = () => {},
2123
enableDnd = false,
24+
onShowDeviceDetails,
2225
}: Props) => {
2326
const dispatch = useDispatch();
2427
const devices = useSelector(selectDevices);
@@ -81,6 +84,11 @@ const DeviceLabel = ({
8184
{device.width}x{device.height}
8285
</span>
8386
</div>
87+
<Button onClick={() => onShowDeviceDetails(device)}>
88+
<Icon
89+
icon={device.isCustom ? 'ic:baseline-edit' : 'ic:baseline-info'}
90+
/>
91+
</Button>
8492
</div>
8593
);
8694
};

0 commit comments

Comments
 (0)