mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-10 06:44:13 +00:00
add btns
This commit is contained in:
parent
df626db4be
commit
5a849162d8
9 changed files with 308 additions and 81 deletions
|
@ -0,0 +1,7 @@
|
|||
export const Accordion = ({ children }: { children: JSX.Element }) => {
|
||||
return (
|
||||
<div id="accordion-open" data-accordion="open">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
type AccordionItemProps = {
|
||||
title: string;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export const AccordionItem = ({ title, children }: AccordionItemProps) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
const toggle = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between gap-3 border border-gray-200 p-5 font-medium text-gray-500 hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:focus:ring-gray-800"
|
||||
onClick={toggle}
|
||||
aria-expanded={isOpen}
|
||||
aria-controls={`accordion-body-${title}`}
|
||||
>
|
||||
<span className="flex items-center">{title}</span>
|
||||
<svg
|
||||
className={`h-3 w-3 ${isOpen ? 'rotate-180' : ''} shrink-0`}
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 10 6"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5 5 1 1 5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id={`accordion-body-${title}`}
|
||||
className={`${isOpen ? 'block' : 'hidden'}`}
|
||||
aria-labelledby={`accordion-heading-${title}`}
|
||||
>
|
||||
<div className="border border-b-0 border-gray-200 p-5 dark:border-gray-700 dark:bg-gray-900">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
2
desktop-app/src/renderer/components/Accordion/index.tsx
Normal file
2
desktop-app/src/renderer/components/Accordion/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { Accordion } from './Accordion';
|
||||
export { AccordionItem } from './AccordionItem';
|
62
desktop-app/src/renderer/components/ConfirmDialog/index.tsx
Normal file
62
desktop-app/src/renderer/components/ConfirmDialog/index.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import Button from '../Button';
|
||||
import Modal from '../Modal';
|
||||
|
||||
export const ConfirmDialog = ({
|
||||
onClose,
|
||||
onConfirm,
|
||||
open,
|
||||
confirmText,
|
||||
}: {
|
||||
onClose?: () => void;
|
||||
onConfirm?: () => void;
|
||||
open: boolean;
|
||||
confirmText?: string;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(open);
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(open);
|
||||
}, [open]);
|
||||
|
||||
const handleClose = () => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (onConfirm) {
|
||||
onConfirm();
|
||||
}
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={handleClose}>
|
||||
<div
|
||||
data-testid="confirm-dialog"
|
||||
className="mb-6 flex h-full w-full flex-col flex-wrap items-center justify-center bg-opacity-95"
|
||||
>
|
||||
<h2 className="m-4 text-center text-2xl font-bold text-white">
|
||||
<p>{confirmText || 'Are you sure?'}</p>
|
||||
</h2>
|
||||
<div className="m-4 flex justify-center">
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
className="me-2 mr-4 mb-2 rounded-lg bg-gray-800 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700"
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
className="me-2 mb-2 rounded-lg border border-gray-300 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:hover:border-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-700 "
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -3,14 +3,20 @@ import Button from 'renderer/components/Button';
|
|||
import { useState } from 'react';
|
||||
import { FileUploader } from 'renderer/components/FileUploader';
|
||||
import Modal from 'renderer/components/Modal';
|
||||
import { addSuites } from 'renderer/store/features/device-manager';
|
||||
import {
|
||||
addSuites,
|
||||
deleteAllSuites,
|
||||
} from 'renderer/store/features/device-manager';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { ConfirmDialog } from 'renderer/components/ConfirmDialog';
|
||||
import { transformFile } from './utils';
|
||||
import { onFileDownload, setCustomDevices } from './helpers';
|
||||
import { ManageSuitesToolError } from './ManageSuitesToolError';
|
||||
|
||||
export const ManageSuitesTool = ({ setCustomDevicesState }: any) => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [resetConfirmation, setResetConfirmation] = useState<boolean>(false);
|
||||
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
@ -39,32 +45,48 @@ export const ManageSuitesTool = ({ setCustomDevicesState }: any) => {
|
|||
setOpen(false);
|
||||
};
|
||||
|
||||
const clearCustomDevices = () => {
|
||||
window.electron.store.set('deviceManager.customDevices', []);
|
||||
setCustomDevicesState([]);
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
dispatch(deleteAllSuites());
|
||||
clearCustomDevices();
|
||||
setResetConfirmation(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row content-end justify-end">
|
||||
<Button
|
||||
data-testid="upload-btn"
|
||||
data-testid="download-btn"
|
||||
className="aspect-square w-12 rounded-full hover:!bg-slate-500"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Icon
|
||||
icon="mdi:folder-upload"
|
||||
fontSize={18}
|
||||
onClick={() => setOpen(true)}
|
||||
/>
|
||||
<Icon icon="uil:export" fontSize={18} />
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="download-btn"
|
||||
data-testid="upload-btn"
|
||||
className="aspect-square w-12 rounded-full hover:!bg-slate-500"
|
||||
onClick={onFileDownload}
|
||||
>
|
||||
<Icon
|
||||
icon="mdi:folder-download"
|
||||
fontSize={18}
|
||||
onClick={onFileDownload}
|
||||
/>
|
||||
<Icon icon="uil:import" fontSize={18} />
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="reset-btn"
|
||||
className="aspect-square w-12 rounded-full hover:!bg-slate-500"
|
||||
onClick={() => setResetConfirmation(true)}
|
||||
>
|
||||
<Icon icon="uil:redo" fontSize={18} />
|
||||
</Button>
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
onConfirm={onReset}
|
||||
onClose={() => setResetConfirmation(false)}
|
||||
open={resetConfirmation}
|
||||
confirmText="Do you want to reset all settings?"
|
||||
/>
|
||||
<Modal
|
||||
isOpen={open}
|
||||
onClose={() => setOpen(false)}
|
||||
|
@ -78,7 +100,9 @@ export const ManageSuitesTool = ({ setCustomDevicesState }: any) => {
|
|||
/>
|
||||
<div className="text-align align-items-center flex flex-row flex-nowrap text-orange-500">
|
||||
<Icon icon="mdi:alert" />
|
||||
<p className="pl-2">Importing will replace all current settings.</p>
|
||||
<p className="pl-2">
|
||||
Duplicated imports will replace existing suites or custom devices.
|
||||
</p>
|
||||
</div>
|
||||
{error && <ManageSuitesToolError onClose={onErrorClose} />}
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"customDevices": [
|
||||
{
|
||||
"id": "123",
|
||||
"name": "a new test",
|
||||
"width": 400,
|
||||
"height": 600,
|
||||
"userAgent": "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",
|
||||
"typse": "phone",
|
||||
"dpxxi": 1,
|
||||
"isTouchCapable": true,
|
||||
"isMobileCapable": true,
|
||||
"capabilities": [
|
||||
"touch",
|
||||
"mobile"
|
||||
],
|
||||
"isCustom": true
|
||||
}
|
||||
],
|
||||
"suites": [
|
||||
{
|
||||
"id": "default",
|
||||
"name": "Default",
|
||||
"devices": [
|
||||
"10008"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a4c142fc-debd-4eaa-beba-aef60093151c",
|
||||
"name": "my custom suite",
|
||||
"devices": [
|
||||
"10008",
|
||||
"30014"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -18,12 +18,6 @@ export const PreviewSuites = () => {
|
|||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className=" mb-6 flex w-full flex-row items-center justify-between gap-2 text-lg">
|
||||
<div className="align-items-center flex flex-row justify-center">
|
||||
<Icon icon="heroicons:swatch" />{' '}
|
||||
<p className="pl-2">Preview Suites</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-4 overflow-x-auto">
|
||||
<div className="flex flex-shrink-0 gap-4">
|
||||
{suites.map((suite) => (
|
||||
|
|
|
@ -17,6 +17,7 @@ import DeviceDetailsModal from './DeviceDetailsModal';
|
|||
import { PreviewSuites } from './PreviewSuites';
|
||||
import { ManageSuitesTool } from './PreviewSuites/ManageSuitesTool/ManageSuitesTool';
|
||||
import { Divider } from '../Divider';
|
||||
import { AccordionItem, Accordion } from '../Accordion';
|
||||
|
||||
const filterDevices = (devices: Device[], filter: string) => {
|
||||
const sanitizedFilter = filter.trim().toLowerCase();
|
||||
|
@ -35,7 +36,7 @@ const DeviceManager = () => {
|
|||
);
|
||||
const dispatch = useDispatch();
|
||||
const activeSuite = useSelector(selectActiveSuite);
|
||||
const devices = activeSuite.devices.map((id) => getDevicesMap()[id]);
|
||||
const devices = activeSuite.devices?.map((id) => getDevicesMap()[id]);
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [filteredDevices, setFilteredDevices] =
|
||||
useState<Device[]>(defaultDevices);
|
||||
|
@ -89,18 +90,27 @@ const DeviceManager = () => {
|
|||
|
||||
return (
|
||||
<div className="mx-auto flex w-4/5 flex-col gap-4 rounded-lg p-8">
|
||||
<div className="flex w-full text-3xl">
|
||||
<span className="w-full text-left">Device Manager</span>
|
||||
<div className="flex w-full justify-end text-3xl">
|
||||
<Button onClick={() => dispatch(setAppView(APP_VIEWS.BROWSER))}>
|
||||
<Icon icon="ic:round-close" />
|
||||
<Icon icon="ic:round-close" fontSize={18} />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="">
|
||||
<ManageSuitesTool setCustomDevicesState={setCustomDevices} />
|
||||
<div>
|
||||
<div className="flex items-center justify-end justify-between ">
|
||||
<h2 className="text-2xl font-bold">Device Manager</h2>
|
||||
<ManageSuitesTool setCustomDevicesState={setCustomDevices} />
|
||||
</div>
|
||||
<Divider />
|
||||
<PreviewSuites />
|
||||
<Accordion>
|
||||
<AccordionItem title="MANAGE SUITES">
|
||||
<PreviewSuites />
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Divider />
|
||||
<div className="my-4 flex items-center justify-end ">
|
||||
<div className="my-4 flex items-start justify-end justify-between">
|
||||
<div className="flex w-fit flex-col items-start px-1">
|
||||
<h2 className="text-2xl font-bold">Manage Devices</h2>
|
||||
</div>
|
||||
<div className="flex w-fit items-center bg-white px-1 dark:bg-slate-900">
|
||||
<Icon icon="ic:outline-search" height={24} />
|
||||
<input
|
||||
|
@ -111,55 +121,70 @@ const DeviceManager = () => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 mb-6 flex justify-between">
|
||||
<div className="text-lg ">Predefined Devices</div>
|
||||
</div>
|
||||
<div className="ml-4 flex flex-row flex-wrap gap-4">
|
||||
{filteredDevices.map((device) => (
|
||||
<DeviceLabel
|
||||
device={device}
|
||||
key={device.id}
|
||||
onShowDeviceDetails={onShowDeviceDetails}
|
||||
disableSelectionControls={
|
||||
devices.find((d) => d.id === device.id) != null &&
|
||||
devices.length === 1
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{filteredDevices.length === 0 ? (
|
||||
<div className="m-10 flex w-full items-center justify-center">
|
||||
Sorry, no matching devices found.
|
||||
<Icon icon="mdi:emoticon-sad-outline" className="ml-1" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-8 mb-6 flex justify-between">
|
||||
<div className="text-lg ">Custom Devices</div>
|
||||
<Button onClick={() => setIsDetailsModalOpen(true)} isActive>
|
||||
<Icon icon="ic:baseline-add" />
|
||||
Add Custom Device
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ml-4 flex flex-row flex-wrap gap-4">
|
||||
{filteredCustomDevices.map((device) => (
|
||||
<DeviceLabel
|
||||
device={device}
|
||||
key={device.id}
|
||||
onShowDeviceDetails={onShowDeviceDetails}
|
||||
/>
|
||||
))}
|
||||
{customDevices.length === 0 ? (
|
||||
<div className="m-10 flex w-full items-center justify-center">
|
||||
No custom devices added yet!
|
||||
</div>
|
||||
) : null}
|
||||
{customDevices.length > 0 && filteredCustomDevices.length === 0 ? (
|
||||
<div className="m-10 flex w-full items-center justify-center">
|
||||
Sorry, no matching devices found.
|
||||
<Icon icon="mdi:emoticon-sad-outline" className="ml-1" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Accordion>
|
||||
<>
|
||||
<AccordionItem title="DEFAULT DEVICES">
|
||||
<div className="ml-4 flex flex-row flex-wrap gap-4">
|
||||
{filteredDevices.map((device) => (
|
||||
<DeviceLabel
|
||||
device={device}
|
||||
key={device.id}
|
||||
onShowDeviceDetails={onShowDeviceDetails}
|
||||
disableSelectionControls={
|
||||
devices.find((d) => d.id === device.id) != null &&
|
||||
devices.length === 1
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{filteredDevices.length === 0 ? (
|
||||
<div className="m-10 flex w-full items-center justify-center">
|
||||
Sorry, no matching devices found.
|
||||
<Icon icon="mdi:emoticon-sad-outline" className="ml-1" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</AccordionItem>
|
||||
<AccordionItem title="CUSTOM DEVICES">
|
||||
<div className="ml-4 flex flex-row flex-wrap gap-4">
|
||||
{filteredCustomDevices.map((device) => (
|
||||
<DeviceLabel
|
||||
device={device}
|
||||
key={device.id}
|
||||
onShowDeviceDetails={onShowDeviceDetails}
|
||||
/>
|
||||
))}
|
||||
{customDevices.length === 0 ? (
|
||||
<div className="m-10 flex w-full flex-col items-center justify-center">
|
||||
<span>No custom devices added yet!</span>
|
||||
<Button
|
||||
className="m-4 rounded-l"
|
||||
onClick={() => setIsDetailsModalOpen(true)}
|
||||
isActive
|
||||
>
|
||||
<Icon icon="ic:baseline-add" />
|
||||
<span className="pr-2 pl-2">Add Custom Device</span>
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{customDevices.length > 0 &&
|
||||
filteredCustomDevices.length === 0 ? (
|
||||
<div className="m-10 flex w-full items-center justify-center">
|
||||
Sorry, no matching devices found.
|
||||
<Icon icon="mdi:emoticon-sad-outline" className="ml-1" />
|
||||
</div>
|
||||
) : null}
|
||||
<Button
|
||||
className={customDevices.length < 1 ? 'hidden' : 'rounded-l'}
|
||||
onClick={() => setIsDetailsModalOpen(true)}
|
||||
isActive
|
||||
>
|
||||
<Icon icon="ic:baseline-add" />
|
||||
<span className="pr-2 pl-2">Add Custom Device</span>
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</>
|
||||
</Accordion>
|
||||
</div>
|
||||
<DeviceDetailsModal
|
||||
onSaveDevice={onSaveDevice}
|
||||
|
|
|
@ -71,10 +71,24 @@ export const deviceManagerSlice = createSlice({
|
|||
window.electron.store.set('deviceManager.previewSuites', suites);
|
||||
},
|
||||
addSuites(state, action: PayloadAction<PreviewSuite[]>) {
|
||||
window.electron.store.set('deviceManager.previewSuites', []);
|
||||
state.suites = action.payload;
|
||||
const existingSuites = window.electron.store.get(
|
||||
'deviceManager.previewSuites'
|
||||
);
|
||||
|
||||
const suitesMap = new Map();
|
||||
action.payload.forEach((suite) => suitesMap.set(suite.name, suite));
|
||||
|
||||
existingSuites.forEach((suite: PreviewSuite) => {
|
||||
if (!suitesMap.has(suite.name)) {
|
||||
suitesMap.set(suite.name, suite);
|
||||
}
|
||||
});
|
||||
|
||||
const mergedSuites = Array.from(suitesMap.values());
|
||||
|
||||
state.suites = mergedSuites;
|
||||
state.activeSuite = action.payload[0].id;
|
||||
window.electron.store.set('deviceManager.previewSuites', action.payload);
|
||||
window.electron.store.set('deviceManager.previewSuites', mergedSuites);
|
||||
},
|
||||
deleteSuite(state, action: PayloadAction<string>) {
|
||||
const suites: PreviewSuite[] = window.electron.store.get(
|
||||
|
@ -89,6 +103,13 @@ export const deviceManagerSlice = createSlice({
|
|||
state.activeSuite = suites[0].id;
|
||||
window.electron.store.set('deviceManager.previewSuites', suites);
|
||||
},
|
||||
deleteAllSuites(state) {
|
||||
const suites: PreviewSuite[] = window.electron.store.get(
|
||||
'deviceManager.previewSuites'
|
||||
);
|
||||
window.electron.store.set('deviceManager.previewSuites', []);
|
||||
state.suites = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -98,8 +119,9 @@ export const {
|
|||
setSuiteDevices,
|
||||
setActiveSuite,
|
||||
addSuite,
|
||||
deleteSuite,
|
||||
addSuites,
|
||||
deleteSuite,
|
||||
deleteAllSuites,
|
||||
} = deviceManagerSlice.actions;
|
||||
|
||||
export const selectSuites = (state: RootState) => state.deviceManager.suites;
|
||||
|
|
Loading…
Reference in a new issue