Add PreviewSuites, moved device manager to PreviewSuiteSelector

This commit is contained in:
Manoj Vivek 2023-04-04 15:14:23 +05:30
parent 0c2de8f75a
commit fcf4430bdf
10 changed files with 165 additions and 54 deletions

View file

@ -17,6 +17,7 @@ interface Props {
enableDnd?: boolean;
moveDevice?: (device: Device, atIndex: number) => void;
onShowDeviceDetails: (device: Device) => void;
hideSelectionControls?: boolean;
disableSelectionControls?: boolean;
}
@ -25,6 +26,7 @@ const DeviceLabel = ({
moveDevice = () => {},
enableDnd = false,
onShowDeviceDetails,
hideSelectionControls = false,
disableSelectionControls = false,
}: Props) => {
const dispatch = useDispatch();
@ -72,9 +74,10 @@ const DeviceLabel = ({
{enableDnd ? <Icon icon="ic:baseline-drag-indicator" /> : null}
<input
className={cx({
'pointer-events-none opacity-0': disableSelectionControls,
'pointer-events-none opacity-0': hideSelectionControls,
})}
type="checkbox"
disabled={disableSelectionControls}
checked={devices.find((d) => d.name === device.name) != null}
onChange={(e) => {
if (e.target.checked) {

View file

@ -0,0 +1,58 @@
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { v4 as uuidv4 } from 'uuid';
import { addSuite } from 'renderer/store/features/device-manager';
import Button from '../../../Button';
import Input from '../../../Input';
import Modal from '../../../Modal';
interface Props {
isOpen: boolean;
onClose: () => void;
}
export const CreateSuiteModal = ({ isOpen, onClose }: Props) => {
const [name, setName] = useState<string>('');
const dispatch = useDispatch();
const handleAddSuite = async (): Promise<void> => {
if (name === '') {
// eslint-disable-next-line no-alert
return alert(
'Suite name cannot be empty. Please enter a name for the suite.'
);
}
dispatch(addSuite({ id: uuidv4(), name, devices: ['10008'] }));
return onClose();
};
return (
<>
<Modal isOpen={isOpen} onClose={onClose} title="Add Suite">
<div className="flex flex-col gap-4">
<div className="flex w-[420px] flex-col gap-2">
<Input
label="Suite Name"
type="text"
placeholder="My Custom Suite"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="flex flex-row justify-between">
<div className="flex flex-row justify-end gap-2">
<Button className="px-2" onClick={onClose}>
Cancel
</Button>
<Button className="px-2" onClick={handleAddSuite} isActive>
Add
</Button>
</div>
</div>
</div>
</Modal>
</>
);
};

View file

@ -0,0 +1,20 @@
import { Icon } from '@iconify/react';
import { useState } from 'react';
import Button from 'renderer/components/Button';
import { CreateSuiteModal } from './CreateSuiteModal';
export const CreateSuiteButton = () => {
const [open, setOpen] = useState<boolean>(false);
return (
<div className="relative flex aspect-square h-full min-h-52 flex-shrink-0 flex-col items-center justify-center gap-4 rounded bg-white dark:bg-slate-900">
<span className="absolute top-12">Add Suite</span>
<Button
className="aspect-square w-16 rounded-full"
onClick={() => setOpen(true)}
>
<Icon icon="mdi:plus" fontSize={30} />
</Button>
<CreateSuiteModal isOpen={open} onClose={() => setOpen(false)} />
</div>
);
};

View file

@ -28,7 +28,7 @@ export const Suite = ({ suite: { id, name, devices }, isActive }: Props) => {
return (
<div
className={cx(
'relative flex-shrink-0 rounded bg-white dark:bg-slate-900',
'relative min-w-56 flex-shrink-0 rounded bg-white dark:bg-slate-900',
{
'border-2 border-slate-500 ': isActive,
}
@ -51,7 +51,8 @@ export const Suite = ({ suite: { id, name, devices }, isActive }: Props) => {
<DeviceLabel
device={getDevicesMap()[deviceId]}
onShowDeviceDetails={() => {}}
disableSelectionControls={!isActive}
hideSelectionControls={!isActive}
disableSelectionControls={devices.length === 1}
enableDnd={isActive}
key={deviceId}
moveDevice={moveDevice}

View file

@ -5,6 +5,7 @@ import {
selectActiveSuite,
selectSuites,
} from 'renderer/store/features/device-manager';
import { CreateSuiteButton } from './CreateSuiteButton';
import { Suite } from './Suite';
export const PreviewSuites = () => {
@ -14,20 +15,17 @@ export const PreviewSuites = () => {
return (
<div className="flex flex-col">
<p className="mb-6 text-lg">Preview Suites</p>
<div className="flex w-full gap-4 overflow-x-auto">
{suites.map((suite) => (
<Suite
suite={suite}
isActive={suite.id === activeSuite.id}
key={suite.name}
/>
))}
<div className="flex aspect-square h-full min-h-52 flex-shrink-0 flex-col items-center justify-center gap-4 rounded bg-white dark:bg-slate-900">
Add Suite
<Button className="aspect-square w-16 rounded-full">
<Icon icon="mdi:plus" fontSize={30} />
</Button>
<div className="flex w-full items-center gap-4 overflow-x-auto">
<div className="flex gap-4">
{suites.map((suite) => (
<Suite
suite={suite}
isActive={suite.id === activeSuite.id}
key={suite.name}
/>
))}
</div>
<CreateSuiteButton />
</div>
</div>
);

View file

@ -106,6 +106,10 @@ const DeviceManager = () => {
device={device}
key={device.name}
onShowDeviceDetails={onShowDeviceDetails}
disableSelectionControls={
devices.find((d) => d.id === device.id) != null &&
devices.length === 1
}
/>
))}
{filteredDevices.length === 0 ? (

View file

@ -3,14 +3,20 @@ import { Icon } from '@iconify/react';
import cx from 'classnames';
import { Fragment, useEffect, useRef, useState } from 'react';
interface Separator {
type: 'separator';
}
interface Option {
type?: 'option';
label: JSX.Element | string;
onClick: () => void;
}
type OptionOrSeparator = Option | Separator;
interface Props {
label: JSX.Element | string;
options: Option[];
options: OptionOrSeparator[];
}
export function DropDown({ label, options }: Props) {
@ -32,24 +38,32 @@ export function DropDown({ label, options }: Props) {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-50 mt-2 w-56 origin-top-right divide-y divide-slate-100 rounded-md bg-slate-100 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-slate-900">
<Menu.Items className="absolute right-0 z-50 mt-2 w-fit origin-top-right divide-y divide-slate-100 rounded-md bg-slate-100 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-slate-900">
<div className="px-1 py-1 ">
{options.map((option) => (
<Menu.Item>
{({ active }) => (
<button
className={cx(
'group flex w-full items-center rounded-md px-2 py-2 text-sm',
{ 'bg-slate-200 dark:bg-slate-800': active }
)}
type="button"
onClick={option.onClick}
>
{option.label}
</button>
)}
</Menu.Item>
))}
{options.map((option, idx) => {
if (option.type === 'separator') {
return (
<div className="m-1 border-t-[1px] border-t-slate-500" />
);
}
return (
// eslint-disable-next-line react/no-array-index-key
<Menu.Item key={idx.toString()}>
{({ active }) => (
<button
className={cx(
'group flex w-full items-center rounded-md px-2 py-2 text-sm',
{ 'bg-slate-200 dark:bg-slate-800': active }
)}
type="button"
onClick={option.onClick}
>
{option.label}
</button>
)}
</Menu.Item>
);
})}
</div>
</Menu.Items>
</Transition>

View file

@ -20,15 +20,6 @@ const MenuFlyout = () => {
<Devtools />
<AllowInSecureSSL />
<ClearHistory />
<Button
onClick={() => {
dispatch(setAppView(APP_VIEWS.DEVICE_MANAGER));
}}
className="w-full !justify-start p-[1px] px-4 "
subtle
>
Device Manager
</Button>
</div>
);
};

View file

@ -1,5 +1,4 @@
import { Icon } from '@iconify/react';
import { active } from 'browser-sync';
import { useDispatch, useSelector } from 'react-redux';
import { DropDown } from 'renderer/components/DropDown';
import {
@ -7,6 +6,7 @@ import {
selectSuites,
setActiveSuite,
} from 'renderer/store/features/device-manager';
import { APP_VIEWS, setAppView } from 'renderer/store/features/ui';
export const PreviewSuiteSelector = () => {
const dispatch = useDispatch();
@ -15,15 +15,30 @@ export const PreviewSuiteSelector = () => {
return (
<DropDown
label={<Icon icon="heroicons:swatch" fontSize={18} />}
options={suites.map((suite) => ({
label: (
<div className="flex w-full items-center justify-between">
<span>{suite.name}</span>
{suite.id === activeSuite.id ? <Icon icon="mdi:check" /> : null}
</div>
),
onClick: () => dispatch(setActiveSuite(suite.id)),
}))}
options={[
...suites.map((suite) => ({
label: (
<div className="flex w-full items-center justify-between gap-12 whitespace-nowrap">
<span>{suite.name}</span>
{suite.id === activeSuite.id ? <Icon icon="mdi:check" /> : null}
</div>
),
onClick: () => dispatch(setActiveSuite(suite.id)),
})),
{
type: 'separator',
},
{
label: (
<div className="flex w-full flex-shrink-0 items-center justify-between gap-12 whitespace-nowrap">
<span>Manage Suites</span>
</div>
),
onClick: () => {
dispatch(setAppView(APP_VIEWS.DEVICE_MANAGER));
},
},
]}
/>
);
};

View file

@ -60,11 +60,18 @@ export const deviceManagerSlice = createSlice({
setActiveSuite(state, action: PayloadAction<string>) {
state.activeSuite = action.payload;
},
addSuite(state, action: PayloadAction<PreviewSuite>) {
const suites = window.electron.store.get('deviceManager.previewSuites');
suites.push(action.payload);
state.suites = suites;
state.activeSuite = action.payload.id;
window.electron.store.set('deviceManager.previewSuites', suites);
},
},
});
// Action creators are generated for each case reducer function
export const { setDevices, setSuiteDevices, setActiveSuite } =
export const { setDevices, setSuiteDevices, setActiveSuite, addSuite } =
deviceManagerSlice.actions;
export const selectSuites = (state: RootState) => state.deviceManager.suites;