Skip to content

Commit

Permalink
Merge branch 'web/auto-reloading-dev-plugins'
Browse files Browse the repository at this point in the history
  • Loading branch information
personalizedrefrigerator committed Dec 24, 2024
2 parents 5bd5312 + f56d022 commit 3ff36fd
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ const usePlugins = (
const reloadAllRef = useRef(false);
reloadAllRef.current ||= pluginRunner !== lastPluginRunner;

useOnDevPluginsUpdated(() => {
logger.info('Dev plugin updated. Reloading...');
reloadAllRef.current = true;
useOnDevPluginsUpdated(async (pluginId: string) => {
logger.info(`Dev plugin ${pluginId} updated. Reloading...`);
await PluginService.instance().unloadPlugin(pluginId);
setReloadCounter(counter => counter + 1);
}, devPluginPath, pluginSupportEnabled);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import shim from '@joplin/lib/shim';
import time from '@joplin/lib/time';
import { join } from 'path';
import { basename, join } from 'path';
import { useRef } from 'react';

type OnDevPluginChange = ()=> void;
type OnDevPluginChange = (id: string)=> void;

const useOnDevPluginsUpdated = (onDevPluginChange: OnDevPluginChange, devPluginPath: string, pluginSupportEnabled: boolean) => {
const onDevPluginChangeRef = useRef(onDevPluginChange);
Expand All @@ -16,17 +16,21 @@ const useOnDevPluginsUpdated = (onDevPluginChange: OnDevPluginChange, devPluginP

const itemToLastModTime = new Map<string, number>();

while (!event.cancelled) {
const publishFolder = join(devPluginPath, 'publish');
const dirStats = await shim.fsDriver().readDirStats(publishFolder);
// publishPath should point to the publish/ subfolder of a plugin's development
// directory.
const checkPluginChange = async (pluginPublishPath: string) => {
const dirStats = await shim.fsDriver().readDirStats(pluginPublishPath);
let hasChange = false;
let changedPluginId = '';
for (const item of dirStats) {
if (item.path.endsWith('.jpl')) {
const lastModTime = itemToLastModTime.get(item.path);
const modTime = item.mtime.getTime();
if (lastModTime === undefined || lastModTime < modTime) {
itemToLastModTime.set(item.path, modTime);
hasChange = true;
changedPluginId = basename(item.path, '.jpl');
break;
}
}
}
Expand All @@ -38,11 +42,17 @@ const useOnDevPluginsUpdated = (onDevPluginChange: OnDevPluginChange, devPluginP
// will always be true, even with no plugin reload.
isFirstUpdateRef.current = false;
} else {
onDevPluginChangeRef.current();
onDevPluginChangeRef.current(changedPluginId);
}
}
};

while (!event.cancelled) {
const publishFolder = join(devPluginPath, 'publish');
await checkPluginChange(publishFolder);

await time.sleep(5);
const pollingIntervalSeconds = 5;
await time.sleep(pollingIntervalSeconds);
}
}, [devPluginPath, pluginSupportEnabled]);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import type FsDriverWeb from '../../../utils/fs-driver/fs-driver-rn.web';
import { IconButton, TouchableRipple } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';

type Mode = 'read'|'readwrite';

interface Props {
themeId: number;
styles: ConfigScreenStyles;
settingMetadata: SettingItem;
mode: 'read'|'readwrite';
mode: Mode;
description: React.ReactNode|null;
updateSettingValue: UpdateSettingValueCallback;
}

Expand All @@ -25,79 +28,86 @@ type ExtendedSelf = (typeof window.self) & {
};
declare const self: ExtendedSelf;

const pathSelectorStyles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
},
mainButton: {
flexGrow: 1,
flexShrink: 1,
padding: 22,
margin: 0,
},
buttonContent: {
flexDirection: 'row',
},
});

const FileSystemPathSelector: FunctionComponent<Props> = props => {
const useFileSystemPath = (settingId: string, updateSettingValue: UpdateSettingValueCallback, accessMode: Mode) => {
const [fileSystemPath, setFileSystemPath] = useState<string>('');

const settingId = props.settingMetadata.key;

useEffect(() => {
setFileSystemPath(Setting.value(settingId));
}, [settingId]);

const selectDirectoryButtonPress = useCallback(async () => {
const showDirectoryPicker = useCallback(async () => {
if (shim.mobilePlatform() === 'web') {
// Directory picker IDs can't include certain characters.
const pickerId = `setting-${settingId}`.replace(/[^a-zA-Z]/g, '_');
const handle = await self.showDirectoryPicker({ id: pickerId, mode: props.mode });
const handle = await self.showDirectoryPicker({ id: pickerId, mode: accessMode });
const fsDriver = shim.fsDriver() as FsDriverWeb;
const uri = await fsDriver.mountExternalDirectory(handle, pickerId, props.mode);
await props.updateSettingValue(settingId, uri);
const uri = await fsDriver.mountExternalDirectory(handle, pickerId, accessMode);
await updateSettingValue(settingId, uri);
setFileSystemPath(uri);
} else {
try {
const doc = await openDocumentTree(true);
if (doc?.uri) {
setFileSystemPath(doc.uri);
await props.updateSettingValue(settingId, doc.uri);
await updateSettingValue(settingId, doc.uri);
} else {
throw new Error('User cancelled operation');
}
} catch (e) {
reg.logger().info('Didn\'t pick sync dir: ', e);
}
}
}, [props.updateSettingValue, settingId, props.mode]);
}, [updateSettingValue, settingId, accessMode]);

const clearPathButtonPress = useCallback(() => {
const clearPath = useCallback(() => {
setFileSystemPath('');
void props.updateSettingValue(settingId, '');
}, [props.updateSettingValue, settingId]);
void updateSettingValue(settingId, '');
}, [updateSettingValue, settingId]);

// Supported on Android and some versions of Chrome
const supported = shim.fsDriver().isUsingAndroidSAF() || (shim.mobilePlatform() === 'web' && 'showDirectoryPicker' in self);
if (!supported) {
return null;
}

return { clearPath, showDirectoryPicker, fileSystemPath, supported };
};

const pathSelectorStyles = StyleSheet.create({
innerContainer: {
paddingTop: 0,
paddingBottom: 0,
paddingLeft: 0,
paddingRight: 0,
},
mainButton: {
flexGrow: 1,
flexShrink: 1,
paddingHorizontal: 16,
paddingVertical: 22,
margin: 0,
},
buttonContent: {
flexDirection: 'row',
},
});

const FileSystemPathSelector: FunctionComponent<Props> = props => {
const settingId = props.settingMetadata.key;
const { clearPath, showDirectoryPicker, fileSystemPath, supported } = useFileSystemPath(settingId, props.updateSettingValue, props.mode);

const styleSheet = props.styles.styleSheet;

const clearButton = (
<IconButton
icon='delete'
accessibilityLabel={_('Clear')}
onPress={clearPathButtonPress}
onPress={clearPath}
/>
);

return <View style={pathSelectorStyles.container}>
const containerStyles = props.styles.getContainerStyle(!!props.description);

const control = <View style={[containerStyles.innerContainer, pathSelectorStyles.innerContainer]}>
<TouchableRipple
onPress={selectDirectoryButtonPress}
onPress={showDirectoryPicker}
style={pathSelectorStyles.mainButton}
role='button'
>
Expand All @@ -112,6 +122,13 @@ const FileSystemPathSelector: FunctionComponent<Props> = props => {
</TouchableRipple>
{fileSystemPath ? clearButton : null}
</View>;

if (!supported) return null;

return <View style={containerStyles.outerContainer}>
{control}
{props.description}
</View>;
};

export default FileSystemPathSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,14 @@ const SettingComponent: React.FunctionComponent<Props> = props => {
} else if (md.type === Setting.TYPE_STRING) {
if (['sync.2.path', 'plugins.devPluginPaths'].includes(md.key) && (shim.fsDriver().isUsingAndroidSAF() || shim.mobilePlatform() === 'web')) {
return (
<View style={containerStyles.outerContainer}>
<FileSystemPathSelector
themeId={props.themeId}
mode={md.key === 'sync.2.path' ? 'readwrite' : 'read'}
styles={props.styles}
settingMetadata={md}
updateSettingValue={props.updateSettingValue}
/>
{descriptionComp}
</View>
<FileSystemPathSelector
themeId={props.themeId}
mode={md.key === 'sync.2.path' ? 'readwrite' : 'read'}
styles={props.styles}
settingMetadata={md}
updateSettingValue={props.updateSettingValue}
description={descriptionComp}
/>
);
}

Expand Down
10 changes: 8 additions & 2 deletions packages/lib/models/settings/builtInMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,11 +931,17 @@ const builtInMetadata = (Setting: typeof SettingType) => {
advanced: true,
appTypes: [AppType.Desktop, AppType.Mobile],
// For now, development plugins are only enabled on desktop & web.
show: () => shim.isElectron() || shim.mobilePlatform() === 'web',
show: (settings) => {
if (shim.isElectron()) return true;
if (shim.mobilePlatform() !== 'web') return false;

const pluginSupportEnabled = settings['plugins.pluginSupportEnabled'];
return !!pluginSupportEnabled;
},
label: () => 'Development plugins',
description: () => {
if (shim.mobilePlatform()) {
return 'The path to a plugin\'s development directory. When rebuilt, the plugin will be reloaded.';
return 'The path to a plugin\'s development directory. When the plugin is rebuilt, Joplin reloads the plugin automatically.';
} else {
return 'You may add multiple plugin paths, each separated by a comma. You will need to restart the application for the changes to take effect.';
}
Expand Down

0 comments on commit 3ff36fd

Please sign in to comment.