1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Multi-profiles: Assign an ID to profiles and remove path

This commit is contained in:
Laurent Cozic 2022-04-16 15:04:06 +01:00
parent 0cdef66816
commit b4a6e17090
11 changed files with 140 additions and 58 deletions

View File

@ -10,13 +10,13 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, profileIndex: number) => {
execute: async (context: CommandContext, profileId: string) => {
const currentConfig = context.state.profileConfig;
if (currentConfig.currentProfile === profileIndex) return;
if (currentConfig.currentProfileId === profileId) return;
const newConfig: ProfileConfig = {
...currentConfig,
currentProfile: profileIndex,
currentProfileId: profileId,
};
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);

View File

@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '../../lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile1',
@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 0);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 0));
},
};
};

View File

@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '../../lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile2',
@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 1);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 1));
},
};
};

View File

@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '../../lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile3',
@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 2);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 2));
},
};
};

View File

@ -19,8 +19,8 @@ export const runtime = (comp: any): CommandRuntime => {
value: '',
onClose: async (answer: string) => {
if (answer) {
const newConfig = await createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfile = newConfig.profiles.length - 1;
const { newConfig, newProfile } = createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfileId = newProfile.id;
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
bridge().restart();
}

View File

@ -86,14 +86,14 @@ const useSwitchProfileMenuItems = (profileConfig: ProfileConfig, menuItemDic: an
menuItem = {
label: profile.name,
click: () => {
void CommandService.instance().execute('switchProfile', i);
void CommandService.instance().execute('switchProfile', profile.id);
},
};
}
menuItem.label = profile.name;
menuItem.type = 'checkbox';
menuItem.checked = profileConfig.currentProfile === i;
menuItem.checked = profileConfig.currentProfileId === profile.id;
switchProfileMenuItems.push(menuItem);
}

View File

@ -15,8 +15,9 @@ const switchToSubProfileSettings = async () => {
const rootProfileDir = Setting.value('profileDir');
const profileConfigPath = `${rootProfileDir}/profiles.json`;
let profileConfig = defaultProfileConfig();
profileConfig = createNewProfile(profileConfig, 'Sub-profile');
profileConfig.currentProfile = 1;
const { newConfig, newProfile } = createNewProfile(profileConfig, 'Sub-profile');
profileConfig = newConfig;
profileConfig.currentProfileId = newProfile.id;
await saveProfileConfig(profileConfigPath, profileConfig);
const { profileDir } = await initProfile(rootProfileDir);
await mkdirp(profileDir);
@ -272,7 +273,7 @@ describe('models/Setting', function() {
expect(Setting.value('style.editor.contentMaxWidth')).toBe(600); // Changed
}));
it('should load sub-profile settings', async () => {
it('should load sub-profile settings - 1', async () => {
await Setting.reset();
Setting.setValue('locale', 'fr_FR'); // Global setting
@ -293,7 +294,7 @@ describe('models/Setting', function() {
expect((await Setting.loadOne('sync.target')).value).toBe(undefined);
});
it('should save sub-profile settings', async () => {
it('should save sub-profile settings - 2', async () => {
await Setting.reset();
Setting.setValue('locale', 'fr_FR'); // Global setting
Setting.setValue('theme', Setting.THEME_DARK); // Global setting

View File

@ -1,7 +1,7 @@
import { writeFile } from 'fs-extra';
import { createNewProfile, getProfileFullPath, loadProfileConfig, saveProfileConfig } from '.';
import { createNewProfile, getProfileFullPath, loadProfileConfig, migrateProfileConfig, saveProfileConfig } from '.';
import { tempFilePath } from '../../testing/test-utils';
import { defaultProfile, defaultProfileConfig, ProfileConfig } from './types';
import { CurrentProfileVersion, defaultProfile, defaultProfileConfig, DefaultProfileId, Profile, ProfileConfig } from './types';
describe('profileConfig/index', () => {
@ -17,7 +17,7 @@ describe('profileConfig/index', () => {
profiles: [
{
name: 'Testing',
path: '.',
id: DefaultProfileId,
},
],
};
@ -26,12 +26,12 @@ describe('profileConfig/index', () => {
const loadedConfig = await loadProfileConfig(filePath);
const expected: ProfileConfig = {
version: 1,
currentProfile: 0,
version: CurrentProfileVersion,
currentProfileId: DefaultProfileId,
profiles: [
{
name: 'Testing',
path: '.',
id: DefaultProfileId,
},
],
};
@ -50,36 +50,71 @@ describe('profileConfig/index', () => {
});
it('should get a profile full path', async () => {
const profile1 = {
const profile1: Profile = {
...defaultProfile(),
path: 'profile-abcd',
id: 'abcd',
};
const profile2 = {
const profile2: Profile = {
...defaultProfile(),
path: '.',
};
const profile3 = {
...defaultProfile(),
path: 'profiles/pro/',
id: DefaultProfileId,
};
expect(getProfileFullPath(profile1, '/test/root')).toBe('/test/root/profile-abcd');
expect(getProfileFullPath(profile2, '/test/root')).toBe('/test/root');
expect(getProfileFullPath(profile3, '/test/root')).toBe('/test/root/profiles/pro');
});
it('should create a new profile', async () => {
let config = defaultProfileConfig();
config = createNewProfile(config, 'new profile 1');
config = createNewProfile(config, 'new profile 2');
const r1 = createNewProfile(config, 'new profile 1');
const r2 = createNewProfile(r1.newConfig, 'new profile 2');
config = r2.newConfig;
expect(config.profiles.length).toBe(3);
expect(config.profiles[1].name).toBe('new profile 1');
expect(config.profiles[2].name).toBe('new profile 2');
expect(config.profiles[1].path).not.toBe(config.profiles[2].path);
expect(config.profiles[1].id).not.toBe(config.profiles[2].id);
});
it('should migrate profile config - version 1 to 2', async () => {
const migrated1 = migrateProfileConfig({
'version': 1,
'currentProfile': 2,
'profiles': [
{
'name': 'Default',
'path': '.',
},
{
'name': 'sub1',
'path': 'profile-sjn25kuh',
},
{
'name': 'sub2',
'path': 'profile-yufzkns3',
},
],
}, 2);
expect(migrated1).toEqual({
'version': 2,
'currentProfileId': 'yufzkns3',
'profiles': [
{
'name': 'Default',
'id': 'default',
},
{
'name': 'sub1',
'id': 'sjn25kuh',
},
{
'name': 'sub2',
'id': 'yufzkns3',
},
],
});
});
});

View File

@ -1,8 +1,34 @@
import { rtrimSlashes, trimSlashes } from '../../path-utils';
import { rtrimSlashes } from '../../path-utils';
import shim from '../../shim';
import { defaultProfile, defaultProfileConfig, Profile, ProfileConfig } from './types';
import { CurrentProfileVersion, defaultProfile, defaultProfileConfig, DefaultProfileId, Profile, ProfileConfig } from './types';
import { customAlphabet } from 'nanoid/non-secure';
export const migrateProfileConfig = (profileConfig: any, toVersion: number): ProfileConfig => {
let version = 2;
while (profileConfig.version < toVersion) {
if (profileConfig.version === 1) {
for (const profile of profileConfig.profiles) {
if (profile.path === '.') {
profile.id = DefaultProfileId;
} else {
profile.id = profile.path.split('-').pop();
}
delete profile.path;
}
const currentProfile = profileConfig.profiles[profileConfig.currentProfile];
profileConfig.currentProfileId = currentProfile.id;
delete profileConfig.currentProfile;
}
profileConfig.version = version;
version++;
}
return profileConfig;
};
export const loadProfileConfig = async (profileConfigPath: string): Promise<ProfileConfig> => {
if (!(await shim.fsDriver().exists(profileConfigPath))) {
return defaultProfileConfig();
@ -10,9 +36,11 @@ export const loadProfileConfig = async (profileConfigPath: string): Promise<Prof
try {
const configContent = await shim.fsDriver().readFile(profileConfigPath, 'utf8');
const parsed = JSON.parse(configContent) as ProfileConfig;
let parsed = JSON.parse(configContent) as ProfileConfig;
if (!parsed.profiles || !parsed.profiles.length) throw new Error(`Profile config should contain at least one profile: ${profileConfigPath}`);
parsed = migrateProfileConfig(parsed, CurrentProfileVersion);
const output: ProfileConfig = {
...defaultProfileConfig(),
...parsed,
@ -25,7 +53,7 @@ export const loadProfileConfig = async (profileConfigPath: string): Promise<Prof
};
}
if (output.currentProfile < 0 || output.currentProfile >= output.profiles.length) throw new Error(`Profile index out of range: ${output.currentProfile}`);
if (!output.profiles.find(p => p.id === output.currentProfileId)) throw new Error(`Current profile ID is invalid: ${output.currentProfileId}`);
return output;
} catch (error) {
error.message = `Could not parse profile configuration: ${profileConfigPath}: ${error.message}`;
@ -38,27 +66,39 @@ export const saveProfileConfig = async (profileConfigPath: string, config: Profi
};
export const getCurrentProfile = (config: ProfileConfig): Profile => {
return { ...config.profiles[config.currentProfile] };
return config.profiles.find(p => p.id === config.currentProfileId);
};
export const getProfileFullPath = (profile: Profile, rootProfilePath: string): string => {
let p = trimSlashes(profile.path);
if (p === '.') p = '';
return rtrimSlashes(`${rtrimSlashes(rootProfilePath)}/${p}`);
const folderName = profile.id === DefaultProfileId ? '' : `/profile-${profile.id}`;
return `${rtrimSlashes(rootProfilePath)}${folderName}`;
};
export const isSubProfile = (profile: Profile): boolean => {
return profile.id !== DefaultProfileId;
};
const profileIdGenerator = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8);
export const createNewProfile = (config: ProfileConfig, profileName: string) => {
const newConfig = {
const newConfig: ProfileConfig = {
...config,
profiles: config.profiles.slice(),
};
newConfig.profiles.push({
const newProfile: Profile = {
name: profileName,
path: `profile-${profileIdGenerator()}`,
});
id: profileIdGenerator(),
};
return newConfig;
newConfig.profiles.push(newProfile);
return {
newConfig: newConfig,
newProfile: newProfile,
};
};
export const profileIdByIndex = (config: ProfileConfig, index: number): string => {
return config.profiles[index].id;
};

View File

@ -1,16 +1,16 @@
import { getCurrentProfile, getProfileFullPath, loadProfileConfig } from '.';
import { getCurrentProfile, getProfileFullPath, isSubProfile, loadProfileConfig } from '.';
import Setting from '../../models/Setting';
export default async (rootProfileDir: string) => {
const profileConfig = await loadProfileConfig(`${rootProfileDir}/profiles.json`);
const profileDir = getProfileFullPath(getCurrentProfile(profileConfig), rootProfileDir);
const isSubProfile = profileConfig.currentProfile !== 0;
Setting.setConstant('isSubProfile', isSubProfile);
const isSub = isSubProfile(getCurrentProfile(profileConfig));
Setting.setConstant('isSubProfile', isSub);
Setting.setConstant('rootProfileDir', rootProfileDir);
Setting.setConstant('profileDir', profileDir);
return {
profileConfig,
profileDir,
isSubProfile,
isSubProfile: isSub,
};
};

View File

@ -1,25 +1,28 @@
export const DefaultProfileId = 'default';
export const CurrentProfileVersion = 2;
export interface Profile {
name: string;
path: string;
id: string;
}
export interface ProfileConfig {
version: number;
currentProfile: number;
currentProfileId: string;
profiles: Profile[];
}
export const defaultProfile = (): Profile => {
return {
name: 'Default',
path: '.',
id: DefaultProfileId,
};
};
export const defaultProfileConfig = (): ProfileConfig => {
return {
version: 1,
currentProfile: 0,
version: CurrentProfileVersion,
currentProfileId: DefaultProfileId,
profiles: [defaultProfile()],
};
};