Skip to content

Commit

Permalink
rework app settings component
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoecheza committed Oct 21, 2024
1 parent 275a1c5 commit 47ea2fe
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 119 deletions.
12 changes: 9 additions & 3 deletions packages/preload/src/modules/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ import path from 'path';
import { produce, type WritableDraft } from 'immer';

import type { Config } from '/shared/types/config';
import { DEFAULT_DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';

import { invoke } from './invoke';
import { getDefaultScenesPath } from './settings';

const CONFIG_FILE_NAME = 'config.json';

let config: Config | undefined;

function getDefaultConfig(): Config {
async function getDefaultConfig(): Promise<Config> {
return {
version: 1,
workspace: {
paths: [],
},
settings: {
scenesPath: await getDefaultScenesPath(),
dependencyUpdateStrategy: DEFAULT_DEPENDENCY_UPDATE_STRATEGY,
},
};
}

Expand All @@ -28,7 +34,7 @@ export async function getConfigPath(): Promise<string> {
const appHome = await invoke('electron.getAppHome');
try {
await fs.stat(appHome);
} catch (error) {
} catch (_) {
await fs.mkdir(appHome);
}
return path.join(appHome, CONFIG_FILE_NAME);
Expand All @@ -48,7 +54,7 @@ export async function getConfig(): Promise<Readonly<Config>> {
config = JSON.parse(await fs.readFile(configPath, 'utf-8')) as Config;
} catch (_) {
try {
await writeConfig(getDefaultConfig());
await writeConfig(await getDefaultConfig());
} catch (e) {
console.error('[Preload] Failed initializing config file', e);
}
Expand Down
24 changes: 13 additions & 11 deletions packages/preload/src/modules/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,33 @@ import {
DEPENDENCY_UPDATE_STRATEGY,
DEFAULT_DEPENDENCY_UPDATE_STRATEGY,
} from '/shared/types/settings';
import type { AppSettings } from '/shared/types/settings';

import { invoke } from './invoke';
import { getConfig, setConfig } from './config';

export async function getScenesPath() {
const config = await getConfig();
return config.scenesPath ?? (await invoke('electron.getAppHome'));
export function getDefaultScenesPath() {
return invoke('electron.getAppHome');
}

export async function setScenesPath(path: string) {
await setConfig(config => (config.scenesPath = path));
export async function getScenesPath() {
const config = await getConfig();
return config.settings.scenesPath ?? (await getDefaultScenesPath());
}

// Helper to check if a value is part of the enum
function isValidUpdateStrategy(value?: string): value is DEPENDENCY_UPDATE_STRATEGY {
export function isValidUpdateStrategy(value?: string): value is DEPENDENCY_UPDATE_STRATEGY {
return Object.values(DEPENDENCY_UPDATE_STRATEGY).includes(value as DEPENDENCY_UPDATE_STRATEGY);
}

export async function getUpdateDependenciesStrategy() {
const { updateDependenciesStrategy } = await getConfig();
if (isValidUpdateStrategy(updateDependenciesStrategy)) return updateDependenciesStrategy;
const { dependencyUpdateStrategy } = (await getConfig()).settings;
if (isValidUpdateStrategy(dependencyUpdateStrategy)) return dependencyUpdateStrategy;
return DEFAULT_DEPENDENCY_UPDATE_STRATEGY;
}

export async function setUpdateDependenciesStrategy(strategy: DEPENDENCY_UPDATE_STRATEGY) {
await setConfig(config => (config.updateDependenciesStrategy = strategy));
export async function updateAppSettings(settings: AppSettings) {
// update app settings on config file
await setConfig(config => (config.settings = settings));
}

export async function selectSceneFolder(): Promise<string | undefined> {
Expand Down
20 changes: 13 additions & 7 deletions packages/preload/src/modules/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { shell } from 'electron';
import equal from 'fast-deep-equal';

import { type DependencyState, SortBy, type Project } from '/shared/types/projects';
import type { Template, Workspace } from '/shared/types/workspace';
import { FileSystemStorage } from '/shared/types/storage';
import { PACKAGES_LIST } from '/shared/types/pkg';
import { DEFAULT_DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';
import { FileSystemStorage } from '/shared/types/storage';
import type { Template, Workspace } from '/shared/types/workspace';

import { getConfig, setConfig } from './config';
import { exists, writeFile as deepWriteFile } from './fs';
Expand All @@ -17,7 +18,7 @@ import { getRowsAndCols, parseCoords } from './scene';
import { getEditorHome } from './editor';
import { invoke } from './invoke';
import { getOudatedDeps } from './npm';
import { getUpdateDependenciesStrategy, getScenesPath } from './settings';
import { getDefaultScenesPath, getScenesPath } from './settings';

import { DEFAULT_THUMBNAIL, NEW_SCENE_NAME, EMPTY_SCENE_TEMPLATE_REPO } from './constants';

Expand Down Expand Up @@ -202,17 +203,22 @@ export async function getTemplates(): Promise<Template[]> {
*/
export async function getWorkspace(): Promise<Workspace> {
const config = await getConfig();
const [projects, missing] = await getProjects(config.workspace.paths);
const templates = await getTemplates();
const updateStrategySetting = await getUpdateDependenciesStrategy();
const [[projects, missing], templates] = await Promise.all([
getProjects(config.workspace.paths),
getTemplates(),
]);

return {
sortBy: SortBy.NEWEST, // TODO: read from editor config file...
projects,
missing,
templates,
settings: {
dependencyUpdateStrategy: updateStrategySetting,
...config.settings,
// TODO: implement migrations for config file...
dependencyUpdateStrategy:
config.settings?.dependencyUpdateStrategy ?? DEFAULT_DEPENDENCY_UPDATE_STRATEGY,
scenesPath: config.settings?.scenesPath ?? (await getDefaultScenesPath()),
},
};
}
Expand Down
81 changes: 28 additions & 53 deletions packages/renderer/src/components/Modals/AppSettings/component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
Box,
Button,
Expand All @@ -14,69 +14,53 @@ import {
import CloseIcon from '@mui/icons-material/Close';
import FolderIcon from '@mui/icons-material/Folder';
import { Modal } from 'decentraland-ui2/dist/components/Modal/Modal';
import equal from 'fast-deep-equal';

import { settings as settingsPreload } from '#preload';

import { DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';

import { t } from '/@/modules/store/translation/utils';
import { useSettings } from '/@/hooks/useSettings';

import './styles.css';

export function AppSettings({ open, onClose }: { open: boolean; onClose: () => void }) {
const settings = useSettings();
const [scenesPath, setScenesPath] = useState('');
const [updateDependenciesStrategy, setUpdateDependenciesStrategy] =
useState<DEPENDENCY_UPDATE_STRATEGY>(DEPENDENCY_UPDATE_STRATEGY.NOTIFY);
const [isDirty, setIsDirty] = useState(false);
const { settings: _settings, updateAppSettings } = useSettings();
const [settings, setSettings] = useState(_settings);

useEffect(() => {
const checkIfDirty = async () => {
const scenesPathSetting = settings.scenesPath;
const updateStrategySetting = settings.updateDependenciesStrategy;

setIsDirty(
scenesPath !== scenesPathSetting || updateDependenciesStrategy !== updateStrategySetting,
);
};
if (!equal(_settings, settings)) setSettings(_settings);
}, [_settings]);

checkIfDirty();
}, [scenesPath, updateDependenciesStrategy]);

const handleChangeSceneFolder = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setScenesPath(event.target.value);
}, []);

const handleClickChangeSceneFolder = useCallback(() => {
settings.setScenesPath(scenesPath);
onClose();
}, [scenesPath]);
const handleChangeSceneFolder = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setSettings({ ...settings, scenesPath: event.target.value });
},
[settings],
);

const handleChangeUpdateDependenciesStrategy = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value as DEPENDENCY_UPDATE_STRATEGY;
setUpdateDependenciesStrategy(value);
setSettings({
...settings,
dependencyUpdateStrategy: event.target.value as DEPENDENCY_UPDATE_STRATEGY,
});
},
[],
[settings],
);

const handleClickApply = useCallback(async () => {
await settings.setScenesPath(scenesPath);
await settings.setUpdateDependenciesStrategy(updateDependenciesStrategy);
const handleClickApply = useCallback(() => {
updateAppSettings(settings);
onClose();
}, [scenesPath, updateDependenciesStrategy]);
}, [settings, updateAppSettings]);

const handleOpenFolder = useCallback(async () => {
const folder = await settingsPreload.selectSceneFolder();
if (folder) {
setScenesPath(folder);
}
}, []);
if (folder) setSettings({ ...settings, scenesPath: folder });
}, [settings]);

useEffect(() => {
const path = settings.scenesPath;
const strategy = settings.updateDependenciesStrategy;
setScenesPath(path);
setUpdateDependenciesStrategy(strategy);
}, [settings.scenesPath, settings.updateDependenciesStrategy]);
const isDirty = useMemo(() => !equal(_settings, settings), [settings, _settings]);

return (
<Modal
Expand All @@ -99,7 +83,7 @@ export function AppSettings({ open, onClose }: { open: boolean; onClose: () => v
</Typography>
<OutlinedInput
color="secondary"
value={scenesPath}
value={settings.scenesPath}
onChange={handleChangeSceneFolder}
endAdornment={
<InputAdornment position="end">
Expand All @@ -112,22 +96,13 @@ export function AppSettings({ open, onClose }: { open: boolean; onClose: () => v
</InputAdornment>
}
/>
<Button
className="ChangeSceneFolderButton"
variant="contained"
color="secondary"
disableRipple
onClick={handleClickChangeSceneFolder}
>
{t('modal.app_settings.fields.scenes_folder.change_button')}
</Button>
</FormGroup>
<FormGroup sx={{ gap: '16px' }}>
<Typography variant="body1">
{t('modal.app_settings.fields.scene_editor_dependencies.label')}
</Typography>
<RadioGroup
value={updateDependenciesStrategy}
value={settings.dependencyUpdateStrategy}
onChange={handleChangeUpdateDependenciesStrategy}
>
<FormControlLabel
Expand Down
1 change: 0 additions & 1 deletion packages/renderer/src/hooks/useEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export const useEditor = () => {
const updateProject = useCallback(
(updatedProject: Project) => {
if (!project || !updatedProject || project.path !== updatedProject.path) return;

dispatch(workspaceActions.updateProject(updatedProject));
},
[workspaceActions.updateProject, project],
Expand Down
41 changes: 11 additions & 30 deletions packages/renderer/src/hooks/useSettings.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
import { useCallback, useEffect, useState } from 'react';
import { settings } from '#preload';
import { DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';
import { useCallback } from 'react';

export const useSettings = () => {
const [scenesPath, setScenesPath] = useState('');
const [updateDependenciesStrategy, setUpdateDependenciesStrategy] =
useState<DEPENDENCY_UPDATE_STRATEGY>(DEPENDENCY_UPDATE_STRATEGY.NOTIFY);
import { useDispatch, useSelector } from '#store';

const handleUpdateScenesPath = useCallback(async (path: string) => {
await settings.setScenesPath(path);
setScenesPath(path);
}, []);
import { type AppSettings } from '/shared/types/settings';

const handleUpdateDependenciesStrategy = useCallback(
async (strategy: DEPENDENCY_UPDATE_STRATEGY) => {
await settings.setUpdateDependenciesStrategy(strategy);
setUpdateDependenciesStrategy(strategy);
},
[],
);
import { actions as workspaceActions } from '/@/modules/store/workspace';

useEffect(() => {
async function getAppSettings() {
const path = await settings.getScenesPath();
const strategy = await settings.getUpdateDependenciesStrategy();
setScenesPath(path);
setUpdateDependenciesStrategy(strategy);
}
export const useSettings = () => {
const dispatch = useDispatch();
const { settings } = useSelector(state => state.workspace);

getAppSettings();
const updateAppSettings = useCallback((settings: AppSettings) => {
dispatch(workspaceActions.updateSettings(settings));
}, []);

return {
scenesPath,
updateDependenciesStrategy,
setScenesPath: handleUpdateScenesPath,
setUpdateDependenciesStrategy: handleUpdateDependenciesStrategy,
settings,
updateAppSettings,
};
};
10 changes: 7 additions & 3 deletions packages/renderer/src/modules/store/workspace/slice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';

import { type Project, SortBy } from '/shared/types/projects';
import { DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';
import { DEFAULT_DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';
import { type Workspace } from '/shared/types/workspace';

import type { Async } from '/@/modules/async';
Expand All @@ -14,7 +14,8 @@ const initialState: Async<Workspace> = {
missing: [],
templates: [],
settings: {
dependencyUpdateStrategy: DEPENDENCY_UPDATE_STRATEGY.NOTIFY,
scenesPath: '',
dependencyUpdateStrategy: DEFAULT_DEPENDENCY_UPDATE_STRATEGY,
},
status: 'idle',
error: null,
Expand Down Expand Up @@ -164,7 +165,10 @@ export const slice = createSlice({
state.projects[projectIdx] = project;
}
},
);
)
.addCase(thunks.updateSettings.fulfilled, (state, { meta }) => {
state.settings = meta.arg;
});
},
});

Expand Down
5 changes: 3 additions & 2 deletions packages/renderer/src/modules/store/workspace/thunks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { fs, npm, workspace } from '#preload';
import { fs, npm, settings, workspace } from '#preload';

import { createAsyncThunk } from '/@/modules/store/thunk';

import { type Project } from '/shared/types/projects';
import { type DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';
import type { DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings';
import { WorkspaceError } from '/shared/types/workspace';

import { actions } from './index';
Expand Down Expand Up @@ -99,3 +99,4 @@ export const runProject = createAsyncThunk(
return updatedProject;
},
);
export const updateSettings = createAsyncThunk('config/updateSettings', settings.updateAppSettings);
8 changes: 2 additions & 6 deletions packages/shared/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { type DEPENDENCY_UPDATE_STRATEGY } from './settings';
import { type AppSettings } from './settings';

export type Config = {
version: number;
workspace: {
paths: string[];
};
} & AppSettings;

export type AppSettings = {
scenesPath?: string;
updateDependenciesStrategy?: DEPENDENCY_UPDATE_STRATEGY;
settings: AppSettings;
};
Loading

0 comments on commit 47ea2fe

Please sign in to comment.