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:
parent
0cdef66816
commit
b4a6e17090
@ -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);
|
||||
|
@ -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));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
@ -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()],
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user