ProfileSelector.tsx
1 import { Box, Button, ButtonGroup, Card, CardActions, CardHeader, Container, Stack } from "@mui/material"; 2 import { useComputed, useSignal, useSignalEffect } from "@preact/signals-react"; 3 import { useService } from '../../context'; 4 import { uuid } from 'ipmc-core'; 5 import { IConfigurationService, IConfigurationServiceSymbol, IDialogService, IDialogServiceSymbol, IFileExportService, IFileExportServiceSymbol, IPopupService, IPopupServiceSymbol, IProfile } from 'ipmc-interfaces'; 6 import React from "react"; 7 import { useTranslation } from '../../hooks/useTranslation'; 8 import { ProfileEditor } from "./ProfileEditor"; 9 10 export function ProfileSelector(props: { profile?: IProfile, switchProfile: (name: string) => void; }) { 11 const _t = useTranslation(); 12 const fileExportService = useService<IFileExportService>(IFileExportServiceSymbol); 13 const popupService = useService<IPopupService>(IPopupServiceSymbol); 14 const dialogService = useService<IDialogService>(IDialogServiceSymbol); 15 const configService = useService<IConfigurationService>(IConfigurationServiceSymbol); 16 17 const profiles = useSignal<(IProfile)[]>([]); 18 19 useSignalEffect(() => { 20 loadProfiles(); 21 }); 22 23 async function loadProfiles(): Promise<void> { 24 profiles.value = await Promise.all((await configService.getProfiles()).map(async (p) => ({ 25 ...(await configService.getProfile(p)), 26 id: p, 27 }))); 28 } 29 30 const content = useComputed(() => profiles.value.map(p => ( 31 <Card key={p.id}> 32 <CardHeader title={p.name} subheader={p.id} /> 33 <CardActions> 34 <ButtonGroup fullWidth={true}> 35 <Button 36 color={"error"} 37 onClick={() => { 38 dialogService.boolDialog({ 39 title: _t('ConfirmProfileDeletion') 40 }) 41 .then(r => { 42 if (r) { 43 configService.removeProfile(p.id); 44 loadProfiles(); 45 } 46 }); 47 }} 48 >{_t('Delete')}</Button> 49 <Button 50 onClick={() => { 51 fileExportService.exportJson(p, `${p.name}.profile.json`); 52 }} 53 >{_t('Export')}</Button> 54 <Button 55 onClick={() => { 56 popupService.show({ 57 content: (close) => ( 58 <ProfileEditor 59 profile={p} 60 onCancel={close} 61 onSave={() => { 62 close(); 63 loadProfiles(); 64 }} 65 /> 66 ) 67 }); 68 }} 69 >{_t('Edit')}</Button> 70 <Button 71 variant={'contained'} 72 onClick={() => props.switchProfile(p.id)} 73 >{_t('Start')}</Button> 74 </ButtonGroup> 75 </CardActions> 76 </Card> 77 ))); 78 79 return ( 80 <Container> 81 <Stack spacing={1}> 82 {content} 83 <Box> 84 <Button onClick={() => { 85 popupService.show({ 86 content: (close) => ( 87 <ProfileEditor 88 profile={{ 89 id: uuid(), 90 type: 'internal', 91 name: '', 92 libraries: [], 93 }} 94 onCancel={close} 95 onSave={() => { 96 close(); 97 loadProfiles(); 98 }} 99 /> 100 ) 101 }); 102 }}>{_t('AddProfile')}</Button> 103 <Button onClick={() => { 104 dialogService.fileDialog({ 105 title: _t('ChooseImportFile'), 106 accept: 'application/json', 107 }) 108 .then(r => { 109 const id = uuid(); 110 const file = r[0]; 111 const fileReader = new FileReader(); 112 fileReader.onloadend = () => { 113 configService.setProfile(id, { ...JSON.parse(fileReader.result as string), id: id }); 114 loadProfiles(); 115 }; 116 fileReader.readAsText(file); 117 }); 118 }}>{_t('ImportProfile')}</Button> 119 </Box> 120 </Stack> 121 </Container> 122 ); 123 }