Skip to content

Commit

Permalink
Feat: Add custom/global Sandboxes (#1879)
Browse files Browse the repository at this point in the history
  • Loading branch information
SpecialAro authored Oct 12, 2024
1 parent 1e55cee commit 035c581
Show file tree
Hide file tree
Showing 13 changed files with 646 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/@types/stores.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export interface AppStore extends TypedStore {
cacheSize: () => void;
debugInfo: () => void;
enableLongPressServiceHint: boolean;
getSandbox: (serviceId: string) => string | undefined;
}

interface CommunityRecipesStore extends TypedStore {
Expand Down
3 changes: 3 additions & 0 deletions src/actions/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ export default <ActionDefinitions>{
endedDownload: {},
stopDownload: {},
togglePauseDownload: {},
addSandboxService: {},
deleteSandboxService: {},
editSandboxService: {},
};
1 change: 1 addition & 0 deletions src/components/services/content/ServiceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class ServiceView extends Component<IProps, IState> {
setWebviewReference={setWebviewRef}
detachService={detachService}
isSpellcheckerEnabled={isSpellcheckerEnabled}
stores={stores}
/>
</>
)}
Expand Down
23 changes: 21 additions & 2 deletions src/components/services/content/ServiceWebview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { action, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Component, type ReactElement } from 'react';
import ElectronWebView from 'react-electron-web-view';
import type { RealStores } from 'src/stores';
import type ServiceModel from '../../../models/Service';

const debug = require('../../../preload-safe-debug')('Ferdium:Services');
Expand All @@ -15,6 +16,7 @@ interface IProps {
}) => void;
detachService: (options: { service: ServiceModel }) => void;
isSpellcheckerEnabled: boolean;
stores?: RealStores;
}

@observer
Expand Down Expand Up @@ -82,7 +84,22 @@ class ServiceWebview extends Component<IProps> {
}

render(): ReactElement {
const { service, setWebviewReference, isSpellcheckerEnabled } = this.props;
const { service, setWebviewReference, isSpellcheckerEnabled, stores } =
this.props;

const { sandboxServices } = stores!.settings.app;

const { sandboxServices: sandboxes } = stores!.app;

const checkForSandbox = () => {
const sandbox = sandboxes.find(s => s.services.includes(service.id));

if (sandbox) {
return `persist:sandbox-${sandbox.id}`;
}

return service.partition;
};

const preloadScript = join(
__dirname,
Expand All @@ -107,7 +124,9 @@ class ServiceWebview extends Component<IProps> {
autosize
src={service.url}
preload={preloadScript}
partition={service.partition}
partition={
sandboxServices ? checkForSandbox() : 'persist:general-session'
}
onDidAttach={() => {
// Force the event handler to run in a new task.
// This resolves a race condition when the `did-attach` is called,
Expand Down
156 changes: 156 additions & 0 deletions src/components/settings/SandboxServiceTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import AddCircleIcon from '@mui/icons-material/AddCircle';
import DeleteIcon from '@mui/icons-material/Delete';
import { Button, IconButton, TextField } from '@mui/material';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import { inject, observer } from 'mobx-react';
import { type ReactNode, type SyntheticEvent, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import type { StoresProps } from 'src/@types/ferdium-components.types';
import SandboxTransferList from './SandboxTransferList';

const debug = require('../../preload-safe-debug')('Ferdium:Settings');

const messages = defineMessages({
addCustomSandbox: {
id: 'sandbox.addCustomSandbox',
defaultMessage: 'Add a custom sandbox',
},
});

interface TabPanelProps {
children?: ReactNode;
index: number;
value: number;
}

function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;

return (
<div
role="tabpanel"
hidden={value !== index}
id={`vertical-tabpanel-${index}`}
aria-labelledby={`vertical-tab-${index}`}
style={{ width: '100%' }}
{...other}
>
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
</div>
);
}

function a11yProps(index: number) {
return {
id: `vertical-tab-${index}`,
'aria-controls': `vertical-tabpanel-${index}`,
};
}

interface IProps extends StoresProps {}

function SandboxServiceTabs(props: IProps) {
const [value, setValue] = useState(0);

const intl = useIntl();

const { stores, actions } = props;

const { sandboxServices } = stores.app;
const { addSandboxService, editSandboxService, deleteSandboxService } =
actions.app;

const handleChange = (event: SyntheticEvent, newValue: number) => {
debug('handleChange', event, newValue);
setValue(newValue);
};

const handleAddTab = () => {
addSandboxService();
setValue(sandboxServices.length - 1);
};

return (
<Box
sx={{
display: 'flex',
height: '100%',
flexDirection: 'column',
}}
>
<Button
variant="outlined"
startIcon={<AddCircleIcon />}
onClick={handleAddTab}
sx={{
width: 'fit-content',
display: 'flex',
margin: '8px',
justifyContent: 'center',
alignItems: 'center',
}}
>
{intl.formatMessage(messages.addCustomSandbox)}
</Button>

<Box
sx={{
flexGrow: 1,
// bgcolor: 'background.paper',
display: sandboxServices.length === 0 ? 'none' : 'flex',
}}
>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs sandbox"
sx={{
borderRight: 1,
borderColor: 'divider',
minWidth: '20%',
maxWidth: '20%',
}}
>
{sandboxServices?.map((tab, index) => (
<Tab key={tab.id} label={tab.name} {...a11yProps(index)} />
))}
</Tabs>
{sandboxServices?.map((tab, index) => (
<TabPanel key={`${tab.id}-tabpanel`} value={value} index={index}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<TextField
id={`text-${tab.id}`}
variant="outlined"
value={tab.name}
onChange={e => {
editSandboxService({ id: tab.id, name: e.target.value });
}}
/>
<IconButton
onClick={() => {
deleteSandboxService({ id: tab.id });
setValue(value ? value - 1 : 0);
}}
aria-label="delete"
color="error"
>
<DeleteIcon />
</IconButton>
</Box>
<SandboxTransferList
value={value}
actions={actions}
stores={stores}
/>
</TabPanel>
))}
</Box>
</Box>
);
}

export default inject('stores', 'actions')(observer(SandboxServiceTabs));
Loading

0 comments on commit 035c581

Please sign in to comment.