You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-03 23:50:33 +02:00
Desktop: Add Sync Wizard dialog
This commit is contained in:
@ -573,6 +573,9 @@ packages/app-desktop/gui/Sidebar/styles/index.js.map
|
|||||||
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
|
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
|
||||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
||||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
|
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
|
||||||
|
packages/app-desktop/gui/SyncWizard/Dialog.d.ts
|
||||||
|
packages/app-desktop/gui/SyncWizard/Dialog.js
|
||||||
|
packages/app-desktop/gui/SyncWizard/Dialog.js.map
|
||||||
packages/app-desktop/gui/TagList.d.ts
|
packages/app-desktop/gui/TagList.d.ts
|
||||||
packages/app-desktop/gui/TagList.js
|
packages/app-desktop/gui/TagList.js
|
||||||
packages/app-desktop/gui/TagList.js.map
|
packages/app-desktop/gui/TagList.js.map
|
||||||
@ -870,6 +873,9 @@ packages/lib/SyncTargetJoplinServer.js.map
|
|||||||
packages/lib/SyncTargetOneDrive.d.ts
|
packages/lib/SyncTargetOneDrive.d.ts
|
||||||
packages/lib/SyncTargetOneDrive.js
|
packages/lib/SyncTargetOneDrive.js
|
||||||
packages/lib/SyncTargetOneDrive.js.map
|
packages/lib/SyncTargetOneDrive.js.map
|
||||||
|
packages/lib/SyncTargetRegistry.d.ts
|
||||||
|
packages/lib/SyncTargetRegistry.js
|
||||||
|
packages/lib/SyncTargetRegistry.js.map
|
||||||
packages/lib/Synchronizer.d.ts
|
packages/lib/Synchronizer.d.ts
|
||||||
packages/lib/Synchronizer.js
|
packages/lib/Synchronizer.js
|
||||||
packages/lib/Synchronizer.js.map
|
packages/lib/Synchronizer.js.map
|
||||||
@ -927,6 +933,12 @@ packages/lib/fs-driver-node.js.map
|
|||||||
packages/lib/fsDriver.test.d.ts
|
packages/lib/fsDriver.test.d.ts
|
||||||
packages/lib/fsDriver.test.js
|
packages/lib/fsDriver.test.js
|
||||||
packages/lib/fsDriver.test.js.map
|
packages/lib/fsDriver.test.js.map
|
||||||
|
packages/lib/hooks/useElementSize.d.ts
|
||||||
|
packages/lib/hooks/useElementSize.js
|
||||||
|
packages/lib/hooks/useElementSize.js.map
|
||||||
|
packages/lib/hooks/useEventListener.d.ts
|
||||||
|
packages/lib/hooks/useEventListener.js
|
||||||
|
packages/lib/hooks/useEventListener.js.map
|
||||||
packages/lib/htmlUtils.d.ts
|
packages/lib/htmlUtils.d.ts
|
||||||
packages/lib/htmlUtils.js
|
packages/lib/htmlUtils.js
|
||||||
packages/lib/htmlUtils.js.map
|
packages/lib/htmlUtils.js.map
|
||||||
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -558,6 +558,9 @@ packages/app-desktop/gui/Sidebar/styles/index.js.map
|
|||||||
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
|
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
|
||||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
||||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
|
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
|
||||||
|
packages/app-desktop/gui/SyncWizard/Dialog.d.ts
|
||||||
|
packages/app-desktop/gui/SyncWizard/Dialog.js
|
||||||
|
packages/app-desktop/gui/SyncWizard/Dialog.js.map
|
||||||
packages/app-desktop/gui/TagList.d.ts
|
packages/app-desktop/gui/TagList.d.ts
|
||||||
packages/app-desktop/gui/TagList.js
|
packages/app-desktop/gui/TagList.js
|
||||||
packages/app-desktop/gui/TagList.js.map
|
packages/app-desktop/gui/TagList.js.map
|
||||||
@ -855,6 +858,9 @@ packages/lib/SyncTargetJoplinServer.js.map
|
|||||||
packages/lib/SyncTargetOneDrive.d.ts
|
packages/lib/SyncTargetOneDrive.d.ts
|
||||||
packages/lib/SyncTargetOneDrive.js
|
packages/lib/SyncTargetOneDrive.js
|
||||||
packages/lib/SyncTargetOneDrive.js.map
|
packages/lib/SyncTargetOneDrive.js.map
|
||||||
|
packages/lib/SyncTargetRegistry.d.ts
|
||||||
|
packages/lib/SyncTargetRegistry.js
|
||||||
|
packages/lib/SyncTargetRegistry.js.map
|
||||||
packages/lib/Synchronizer.d.ts
|
packages/lib/Synchronizer.d.ts
|
||||||
packages/lib/Synchronizer.js
|
packages/lib/Synchronizer.js
|
||||||
packages/lib/Synchronizer.js.map
|
packages/lib/Synchronizer.js.map
|
||||||
@ -912,6 +918,12 @@ packages/lib/fs-driver-node.js.map
|
|||||||
packages/lib/fsDriver.test.d.ts
|
packages/lib/fsDriver.test.d.ts
|
||||||
packages/lib/fsDriver.test.js
|
packages/lib/fsDriver.test.js
|
||||||
packages/lib/fsDriver.test.js.map
|
packages/lib/fsDriver.test.js.map
|
||||||
|
packages/lib/hooks/useElementSize.d.ts
|
||||||
|
packages/lib/hooks/useElementSize.js
|
||||||
|
packages/lib/hooks/useElementSize.js.map
|
||||||
|
packages/lib/hooks/useEventListener.d.ts
|
||||||
|
packages/lib/hooks/useEventListener.js
|
||||||
|
packages/lib/hooks/useEventListener.js.map
|
||||||
packages/lib/htmlUtils.d.ts
|
packages/lib/htmlUtils.d.ts
|
||||||
packages/lib/htmlUtils.js
|
packages/lib/htmlUtils.js
|
||||||
packages/lib/htmlUtils.js.map
|
packages/lib/htmlUtils.js.map
|
||||||
|
@ -10,7 +10,7 @@ const { cliUtils } = require('./cli-utils.js');
|
|||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const locker = require('proper-lockfile');
|
const locker = require('proper-lockfile');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry').default;
|
||||||
const MigrationHandler = require('@joplin/lib/services/synchronizer/MigrationHandler').default;
|
const MigrationHandler = require('@joplin/lib/services/synchronizer/MigrationHandler').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@ -114,6 +114,10 @@ interface AppStateRoute {
|
|||||||
props: any;
|
props: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppStateDialog {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AppState extends State {
|
export interface AppState extends State {
|
||||||
route: AppStateRoute;
|
route: AppStateRoute;
|
||||||
navHistory: any[];
|
navHistory: any[];
|
||||||
@ -130,6 +134,7 @@ export interface AppState extends State {
|
|||||||
// Extra reducer keys go here
|
// Extra reducer keys go here
|
||||||
watchedResources: any;
|
watchedResources: any;
|
||||||
mainLayout: LayoutItem;
|
mainLayout: LayoutItem;
|
||||||
|
dialogs: AppStateDialog[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const appDefaultState: AppState = {
|
const appDefaultState: AppState = {
|
||||||
@ -150,6 +155,7 @@ const appDefaultState: AppState = {
|
|||||||
layoutMoveMode: false,
|
layoutMoveMode: false,
|
||||||
mainLayout: null,
|
mainLayout: null,
|
||||||
startupPluginsLoaded: false,
|
startupPluginsLoaded: false,
|
||||||
|
dialogs: [],
|
||||||
...resourceEditWatcherDefaultState,
|
...resourceEditWatcherDefaultState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -370,6 +376,30 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'DIALOG_OPEN':
|
||||||
|
|
||||||
|
{
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
const newDialogs = newState.dialogs.slice();
|
||||||
|
|
||||||
|
if (newDialogs.find(d => d.name === action.name)) throw new Error(`This dialog is already opened: ${action.name}`);
|
||||||
|
|
||||||
|
newDialogs.push({
|
||||||
|
name: action.name,
|
||||||
|
});
|
||||||
|
newState.dialogs = newDialogs;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'DIALOG_CLOSE':
|
||||||
|
|
||||||
|
{
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
|
||||||
|
newState.dialogs = newDialogs;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'LAYOUT_MOVE_MODE_SET':
|
case 'LAYOUT_MOVE_MODE_SET':
|
||||||
|
|
||||||
newState = {
|
newState = {
|
||||||
@ -834,6 +864,14 @@ class Application extends BaseApplication {
|
|||||||
// });
|
// });
|
||||||
// }, 5000);
|
// }, 5000);
|
||||||
|
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// this.dispatch({
|
||||||
|
// type: 'DIALOG_OPEN',
|
||||||
|
// name: 'syncWizard',
|
||||||
|
// });
|
||||||
|
// }, 2000);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="43px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 43 40" version="1.1" height="40px">
|
||||||
|
<path d="m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z" fill="#007EE5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 441 B |
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 682.66669 682.66669" height="682.66669" width="682.66669" xml:space="preserve" id="svg2" version="1.1">
|
||||||
|
<defs id="defs6">
|
||||||
|
<linearGradient id="linearGradient26" spreadMethod="pad" gradientTransform="matrix(-4387.91,4387.91,4387.91,4387.91,4753.95,366.05)" gradientUnits="userSpaceOnUse" y2="0" x2="1" y1="0" x1="0">
|
||||||
|
<stop id="stop22" offset="0" style="stop-opacity:1;stop-color:#004caf"/>
|
||||||
|
<stop id="stop24" offset="1" style="stop-opacity:1;stop-color:#1f95f8"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g transform="matrix(1.3333333,0,0,-1.3333333,0,682.66667)" id="g10">
|
||||||
|
<g transform="scale(0.1)" id="g12">
|
||||||
|
<g id="g14">
|
||||||
|
<g clip-path="url(#clipPath20)" id="g16">
|
||||||
|
<path id="path28" style="fill:url(#linearGradient26);fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 3873.89,0 H 1246.11 C 560.754,0 0,560.75 0,1246.11 V 3873.88 C 0,4559.25 560.754,5120 1246.11,5120 H 3873.89 C 4559.25,5120 5120,4559.25 5120,3873.88 V 1246.11 C 5120,560.75 4559.25,0 3873.89,0"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path id="path30" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 3961.59,4435.23 H 2570.18 c -13.15,0 -23.78,-10.64 -23.78,-23.77 v -441.84 c 0,-14.87 12.04,-26.92 26.92,-26.92 h 190.77 c 77.16,0 139.73,-59.35 146.43,-134.77 V 3505 3336.23 1728.75 1717.36 h -0.05 c 0.48,-16.84 -0.19,-33.4 -1.83,-49.71 -0.18,-2.38 -0.5,-4.73 -0.79,-7.09 -1.1,-9.53 -2.32,-19.01 -4.17,-28.29 -1.01,-5.29 -2.44,-10.44 -3.71,-15.65 -1.71,-6.93 -3.09,-13.97 -5.22,-20.75 -12.58,-40.27 -32.47,-77.62 -59.98,-110.5 -1.01,-1.17 -2.26,-2.25 -3.26,-3.41 -8.39,-9.72 -17.2,-19.19 -26.95,-28.06 -9.84,-8.95 -20.26,-17.27 -31.21,-25 -77.84,-55.14 -182.61,-79.4 -299.67,-68.2 -149.26,14.03 -297.34,81.72 -417.03,190.62 -119.67,108.89 -194.08,243.62 -209.48,379.41 -13.85,121.48 22.55,228.38 102.42,301.05 0.21,0.16 0.4,0.31 0.56,0.48 3.09,2.77 6.49,5.2 9.67,7.87 57.16,47.89 131.67,76.91 216.7,84.91 0.96,0.09 1.88,0.24 2.79,0.32 8.95,0.79 18.07,1.15 27.27,1.49 4.81,0.16 9.56,0.5 14.44,0.54 1.62,0.02 3.16,0.19 4.78,0.19 2.9,0 5.91,-0.38 8.81,-0.42 13.4,-0.21 26.9,-0.76 40.67,-1.94 1.74,-0.14 3.4,-0.08 5.19,-0.24 1.27,-0.13 2.53,-0.41 3.8,-0.54 78,-7.82 155.23,-31.11 228.52,-66.4 1.53,-0.07 3.3,-0.54 5.51,-1.76 22.34,-12.34 26.62,0.9 27.28,9.65 v 382.24 282.82 c 0,19.05 -13.25,35.9 -31.83,39.99 -394.76,86.88 -782.08,-3.55 -1055.38,-252.34 -238.75,-217.18 -354.24,-530.58 -316.82,-859.79 33.39,-293.23 183.91,-574.94 423.88,-793.33 233.89,-212.79 531.69,-345.86 838.88,-374.801 42.33,-3.918 84.86,-5.938 126.36,-5.938 293.38,0 565.61,100.598 766.54,283.379 190.34,173.3 304.35,411.27 321.08,670.16 l 1.55,1697.91 h 0.17 v 453.97 h 0.06 v 7.92 c 1.72,80.12 67.05,144.58 147.61,144.58 h 190.77 c 14.86,0 26.92,12.05 26.92,26.92 v 441.84 c 0,13.13 -10.63,23.77 -23.78,23.77"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||||
|
<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
|
||||||
|
<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
|
||||||
|
<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
|
||||||
|
<!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
|
||||||
|
<!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
|
||||||
|
<!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
|
||||||
|
<!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
|
||||||
|
<!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
|
||||||
|
]>
|
||||||
|
<svg version="1.1" id="Livello_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1030.04 659.922"
|
||||||
|
enable-background="new 0 0 1030.04 659.922" xml:space="preserve">
|
||||||
|
<metadata>
|
||||||
|
<sfw xmlns="&ns_sfw;">
|
||||||
|
<slices></slices>
|
||||||
|
<sliceSourceBounds bottomLeftOrigin="true" height="659.922" width="1030.04" x="-490" y="-344.922"></sliceSourceBounds>
|
||||||
|
</sfw>
|
||||||
|
</metadata>
|
||||||
|
<g id="STYLE_COLOR_1_">
|
||||||
|
<path fill="#0364B8" d="M622.292,445.338l212.613-203.327C790.741,69.804,615.338-33.996,443.13,10.168
|
||||||
|
C365.58,30.056,298.224,78.13,254.209,145.005C257.5,144.922,622.292,445.338,622.292,445.338z"/>
|
||||||
|
<path fill="#0078D4" d="M392.776,183.283l-0.01,0.035c-40.626-25.162-87.479-38.462-135.267-38.397
|
||||||
|
c-1.104,0-2.189,0.07-3.291,0.083C112.064,146.765-1.74,263.423,0.02,405.567c0.638,51.562,16.749,101.743,46.244,144.04
|
||||||
|
l318.528-39.894l244.209-196.915L392.776,183.283z"/>
|
||||||
|
<path fill="#1490DF" d="M834.905,242.012c-4.674-0.312-9.371-0.528-14.123-0.528c-28.523-0.028-56.749,5.798-82.93,17.117
|
||||||
|
l-0.006-0.022l-128.844,54.22l142.041,175.456l253.934,61.728c54.799-101.732,16.752-228.625-84.98-283.424
|
||||||
|
c-26.287-14.16-55.301-22.529-85.091-24.546V242.012z"/>
|
||||||
|
<path fill="#28A8EA" d="M46.264,549.607C94.359,618.756,173.27,659.966,257.5,659.922h563.281
|
||||||
|
c76.946,0.022,147.691-42.202,184.195-109.937L609.001,312.798L46.264,549.607z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -11,7 +11,7 @@ import EncryptionConfigScreen from '../EncryptionConfigScreen';
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
const pathUtils = require('@joplin/lib/path-utils');
|
const pathUtils = require('@joplin/lib/path-utils');
|
||||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||||
import ClipperConfigScreen from '../ClipperConfigScreen';
|
import ClipperConfigScreen from '../ClipperConfigScreen';
|
||||||
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
||||||
@ -94,6 +94,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData);
|
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData);
|
||||||
await Setting.saveAll();
|
await Setting.saveAll();
|
||||||
bridge().restart();
|
bridge().restart();
|
||||||
|
} else if (key === 'sync.openSyncWizard') {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'DIALOG_OPEN',
|
||||||
|
name: 'syncWizard',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unhandled key: ${key}`);
|
throw new Error(`Unhandled key: ${key}`);
|
||||||
}
|
}
|
||||||
@ -606,11 +611,15 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (md.type === Setting.TYPE_BUTTON) {
|
} else if (md.type === Setting.TYPE_BUTTON) {
|
||||||
return (
|
const labelComp = md.hideLabel ? null : (
|
||||||
<div key={key} style={rowStyle}>
|
|
||||||
<div style={labelStyle}>
|
<div style={labelStyle}>
|
||||||
<label>{md.label()}</label>
|
<label>{md.label()}</label>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={key} style={rowStyle}>
|
||||||
|
{labelComp}
|
||||||
<Button level={ButtonLevel.Secondary} title={md.label()} onClick={md.onClick ? md.onClick : () => this.handleSettingButton(key)}/>
|
<Button level={ButtonLevel.Secondary} title={md.label()} onClick={md.onClick ? md.onClick : () => this.handleSettingButton(key)}/>
|
||||||
{descriptionComp}
|
{descriptionComp}
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,10 +18,11 @@ const DialogRoot = styled.div`
|
|||||||
background-color: ${props => props.theme.backgroundColor};
|
background-color: ${props => props.theme.backgroundColor};
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
box-shadow: 6px 6px 20px rgba(0,0,0,0.5);
|
box-shadow: 6px 6px 20px rgba(0,0,0,0.5);
|
||||||
margin-top: 20px;
|
margin: 20px;
|
||||||
min-height: fit-content;
|
min-height: fit-content;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
border-radius: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Root = styled.div`
|
const Root = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: ${props => props.justifyContent ? props.justifyContent : 'flex-start'};
|
||||||
font-family: ${props => props.theme.fontFamily};
|
font-family: ${props => props.theme.fontFamily};
|
||||||
font-size: ${props => props.theme.fontSize * 1.5}px;
|
font-size: ${props => props.theme.fontSize * 1.5}px;
|
||||||
line-height: 1.6em;
|
line-height: 1.6em;
|
||||||
@ -12,10 +14,11 @@ const Root = styled.div`
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
justifyContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DialogTitle(props: Props) {
|
export default function DialogTitle(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Root>{props.title}</Root>
|
<Root justifyContent={props.justifyContent}>{props.title}</Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import app from '../app';
|
import app, { AppState, AppStateDialog } from '../app';
|
||||||
import MainScreen from './MainScreen/MainScreen';
|
import MainScreen from './MainScreen/MainScreen';
|
||||||
import ConfigScreen from './ConfigScreen/ConfigScreen';
|
import ConfigScreen from './ConfigScreen/ConfigScreen';
|
||||||
import StatusScreen from './StatusScreen/StatusScreen';
|
import StatusScreen from './StatusScreen/StatusScreen';
|
||||||
@ -10,7 +10,6 @@ import { Size } from './ResizableLayout/utils/types';
|
|||||||
import MenuBar from './MenuBar';
|
import MenuBar from './MenuBar';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
const { render } = require('react-dom');
|
const { render } = require('react-dom');
|
||||||
const { connect, Provider } = require('react-redux');
|
const { connect, Provider } = require('react-redux');
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
@ -19,6 +18,7 @@ import ClipperServer from '@joplin/lib/ClipperServer';
|
|||||||
import DialogTitle from './DialogTitle';
|
import DialogTitle from './DialogTitle';
|
||||||
import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './DialogButtonRow';
|
import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './DialogButtonRow';
|
||||||
import Dialog from './Dialog';
|
import Dialog from './Dialog';
|
||||||
|
import SyncWizardDialog from './SyncWizard/Dialog';
|
||||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||||
const { Navigator } = require('./Navigator.min.js');
|
const { Navigator } = require('./Navigator.min.js');
|
||||||
@ -33,6 +33,7 @@ interface Props {
|
|||||||
size: Size;
|
size: Size;
|
||||||
zoomFactor: number;
|
zoomFactor: number;
|
||||||
needApiAuth: boolean;
|
needApiAuth: boolean;
|
||||||
|
dialogs: AppStateDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModalDialogProps {
|
interface ModalDialogProps {
|
||||||
@ -42,6 +43,24 @@ interface ModalDialogProps {
|
|||||||
onClick: ClickEventHandler;
|
onClick: ClickEventHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RegisteredDialogProps {
|
||||||
|
themeId: number;
|
||||||
|
key: string;
|
||||||
|
dispatch: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegisteredDialog {
|
||||||
|
render: (props: RegisteredDialogProps)=> any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const registeredDialogs: Record<string, RegisteredDialog> = {
|
||||||
|
syncWizard: {
|
||||||
|
render: (props: RegisteredDialogProps) => {
|
||||||
|
return <SyncWizardDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId}/>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const GlobalStyle = createGlobalStyle`
|
const GlobalStyle = createGlobalStyle`
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -151,6 +170,22 @@ class RootComponent extends React.Component<Props, any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderDialogs() {
|
||||||
|
if (!this.props.dialogs.length) return null;
|
||||||
|
|
||||||
|
const output: any[] = [];
|
||||||
|
for (const dialog of this.props.dialogs) {
|
||||||
|
const md = registeredDialogs[dialog.name];
|
||||||
|
if (!md) throw new Error(`Unknown dialog: ${dialog.name}`);
|
||||||
|
output.push(md.render({
|
||||||
|
key: dialog.name,
|
||||||
|
themeId: this.props.themeId,
|
||||||
|
dispatch: this.props.dispatch,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const navigatorStyle = {
|
const navigatorStyle = {
|
||||||
width: this.props.size.width / this.props.zoomFactor,
|
width: this.props.size.width / this.props.zoomFactor,
|
||||||
@ -176,19 +211,21 @@ class RootComponent extends React.Component<Props, any> {
|
|||||||
<GlobalStyle/>
|
<GlobalStyle/>
|
||||||
<Navigator style={navigatorStyle} screens={screens} />
|
<Navigator style={navigatorStyle} screens={screens} />
|
||||||
{this.renderModalMessage(this.modalDialogProps())}
|
{this.renderModalMessage(this.modalDialogProps())}
|
||||||
|
{this.renderDialogs()}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StyleSheetManager>
|
</StyleSheetManager>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
size: state.windowContentSize,
|
size: state.windowContentSize,
|
||||||
zoomFactor: state.settings.windowContentZoomFactor / 100,
|
zoomFactor: state.settings.windowContentZoomFactor / 100,
|
||||||
appState: state.appState,
|
appState: state.appState,
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
needApiAuth: state.needApiAuth,
|
needApiAuth: state.needApiAuth,
|
||||||
|
dialogs: state.dialogs,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
343
packages/app-desktop/gui/SyncWizard/Dialog.tsx
Normal file
343
packages/app-desktop/gui/SyncWizard/Dialog.tsx
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useState, useRef, useCallback } from 'react';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import DialogButtonRow from '../DialogButtonRow';
|
||||||
|
import Dialog from '../Dialog';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import DialogTitle from '../DialogTitle';
|
||||||
|
import SyncTargetRegistry, { SyncTargetInfo } from '@joplin/lib/SyncTargetRegistry';
|
||||||
|
import useElementSize from '@joplin/lib/hooks/useElementSize';
|
||||||
|
import Button, { ButtonLevel } from '../Button/Button';
|
||||||
|
import bridge from '../../services/bridge';
|
||||||
|
import StyledInput from '../style/StyledInput';
|
||||||
|
import Setting from '../../../lib/models/Setting';
|
||||||
|
import SyncTargetJoplinCloud from '../../../lib/SyncTargetJoplinCloud';
|
||||||
|
import StyledLink from '../style/StyledLink';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
themeId: number;
|
||||||
|
dispatch: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledRoot = styled.div`
|
||||||
|
min-width: 500px;
|
||||||
|
max-width: 1200px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SyncTargetDescription = styled.div`
|
||||||
|
${props => props.height ? `height: ${props.height}px` : ''};
|
||||||
|
margin-bottom: 1.3em;
|
||||||
|
line-height: ${props => props.theme.lineHeight};
|
||||||
|
font-size: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CreateAccountLink = styled(StyledLink)`
|
||||||
|
font-size: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ContentRoot = styled.div`
|
||||||
|
background-color: ${props => props.theme.backgroundColor3};
|
||||||
|
padding: 1em;
|
||||||
|
padding-right: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelfHostingMessage = styled.div`
|
||||||
|
color: ${props => props.theme.color};
|
||||||
|
padding-right: 1em;
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: 1em;
|
||||||
|
opacity: 0.6;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SyncTargetBoxes = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SyncTargetTitle = styled.p`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.7em;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SyncTargetLogo = styled.img`
|
||||||
|
height: 1.3em;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SyncTargetBox = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: ${props => props.theme.fontFamily};
|
||||||
|
color: ${props => props.theme.color};
|
||||||
|
background-color: ${props => props.theme.backgroundColor};
|
||||||
|
border: 1px solid ${props => props.theme.dividerColor};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.8em 2.2em 2em 2.2em;
|
||||||
|
margin-right: 1em;
|
||||||
|
max-width: 400px;
|
||||||
|
opacity: ${props => props.faded ? 0.5 : 1};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeatureList = styled.div`
|
||||||
|
margin-bottom: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeatureIcon = styled.i`
|
||||||
|
display: inline-flex;
|
||||||
|
width: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
color: ${props => props.theme.color4};
|
||||||
|
position: absolute;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeatureLine = styled.div`
|
||||||
|
margin-bottom: .5em;
|
||||||
|
opacity: ${props => props.enabled ? 1 : 0.5};
|
||||||
|
position: relative;
|
||||||
|
font-size: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeatureLabel = styled.div`
|
||||||
|
margin-left: 24px;
|
||||||
|
line-height: ${props => props.theme.lineHeight};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectButton = styled(Button)`
|
||||||
|
padding: 10px 10px;
|
||||||
|
height: auto;
|
||||||
|
min-height: auto;
|
||||||
|
max-height: fit-content;
|
||||||
|
font-size: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const JoplinCloudLoginForm = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FormLabel = styled.label`
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 1em 0 0.6em 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const syncTargetNames: string[] = [
|
||||||
|
'joplinCloud',
|
||||||
|
'dropbox',
|
||||||
|
'onedrive',
|
||||||
|
'nextcloud',
|
||||||
|
'webdav',
|
||||||
|
'amazon_s3',
|
||||||
|
'joplinServer',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const logosImageNames: Record<string, string> = {
|
||||||
|
'dropbox': 'Dropbox.svg',
|
||||||
|
'joplinCloud': 'JoplinCloud.svg',
|
||||||
|
'onedrive': 'OneDrive.svg',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function(props: Props) {
|
||||||
|
const [showJoplinCloudForm, setShowJoplinCloudForm] = useState(false);
|
||||||
|
const joplinCloudDescriptionRef = useRef(null);
|
||||||
|
const [joplinCloudEmail, setJoplinCloudEmail] = useState('');
|
||||||
|
const [joplinCloudPassword, setJoplinCloudPassword] = useState('');
|
||||||
|
const [joplinCloudLoginInProgress, setJoplinCloudLoginInProgress] = useState(false);
|
||||||
|
|
||||||
|
function closeDialog(dispatch: Function) {
|
||||||
|
dispatch({
|
||||||
|
type: 'DIALOG_CLOSE',
|
||||||
|
name: 'syncWizard',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onButtonRowClick = useCallback(() => {
|
||||||
|
closeDialog(props.dispatch);
|
||||||
|
}, [props.dispatch]);
|
||||||
|
|
||||||
|
const { height: descriptionHeight } = useElementSize(joplinCloudDescriptionRef);
|
||||||
|
|
||||||
|
function renderFeature(enabled: boolean, label: string) {
|
||||||
|
const className = enabled ? 'fas fa-check' : 'fas fa-times';
|
||||||
|
return (
|
||||||
|
<FeatureLine enabled={enabled} key={label}><FeatureIcon className={className}></FeatureIcon> <FeatureLabel>{label}</FeatureLabel></FeatureLine>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFeatures(name: string) {
|
||||||
|
return (
|
||||||
|
<FeatureList>
|
||||||
|
{[
|
||||||
|
renderFeature(true, _('Sync your notes')),
|
||||||
|
renderFeature(name === 'joplinCloud', _('Publish notes to the internet')),
|
||||||
|
renderFeature(name === 'joplinCloud', _('Collaborate on notebooks with others')),
|
||||||
|
]}
|
||||||
|
</FeatureList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onJoplinCloudEmailChange = useCallback((event: any) => {
|
||||||
|
setJoplinCloudEmail(event.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onJoplinCloudPasswordChange = useCallback((event: any) => {
|
||||||
|
setJoplinCloudPassword(event.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onJoplinCloudLoginClick = useCallback(async () => {
|
||||||
|
setJoplinCloudLoginInProgress(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await SyncTargetJoplinCloud.checkConfig({
|
||||||
|
password: () => joplinCloudPassword,
|
||||||
|
path: () => Setting.value('sync.10.path'),
|
||||||
|
userContentPath: () => Setting.value('sync.10.userContentPath'),
|
||||||
|
username: () => joplinCloudEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
Setting.setValue('sync.target', 10);
|
||||||
|
Setting.setValue('sync.10.username', joplinCloudEmail);
|
||||||
|
Setting.setValue('sync.10.password', joplinCloudPassword);
|
||||||
|
await Setting.saveAll();
|
||||||
|
|
||||||
|
alert(_('Thank you! Your Joplin Cloud account is now setup and ready to use.'));
|
||||||
|
|
||||||
|
closeDialog(props.dispatch);
|
||||||
|
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'Main',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert(_('There was an error setting up your Joplin Cloud account. Please verify your email and password and try again. Error was:\n\n%s', result.errorMessage));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setJoplinCloudLoginInProgress(false);
|
||||||
|
}
|
||||||
|
}, [joplinCloudEmail, joplinCloudPassword, props.dispatch]);
|
||||||
|
|
||||||
|
const onJoplinCloudCreateAccountClick = useCallback(() => {
|
||||||
|
bridge().openExternal('https://joplinapp.org/plans/');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function renderJoplinCloudLoginForm() {
|
||||||
|
return (
|
||||||
|
<JoplinCloudLoginForm>
|
||||||
|
<div>{_('Login below.')} <CreateAccountLink href="#" onClick={onJoplinCloudCreateAccountClick}>{_('Or create an account.')}</CreateAccountLink></div>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<StyledInput type="email" onChange={onJoplinCloudEmailChange}/>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
<StyledInput type="password" onChange={onJoplinCloudPasswordChange}/>
|
||||||
|
<SelectButton mt="1.3em" disabled={joplinCloudLoginInProgress} level={ButtonLevel.Primary} title={_('Login')} onClick={onJoplinCloudLoginClick}/>
|
||||||
|
</JoplinCloudLoginForm>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectButtonClick = useCallback(async (name: string) => {
|
||||||
|
if (name === 'joplinCloud') {
|
||||||
|
setShowJoplinCloudForm(true);
|
||||||
|
} else {
|
||||||
|
Setting.setValue('sync.target', name === 'dropbox' ? 7 : 3);
|
||||||
|
await Setting.saveAll();
|
||||||
|
closeDialog(props.dispatch);
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: name === 'dropbox' ? 'DropboxLogin' : 'OneDriveLogin',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [props.dispatch]);
|
||||||
|
|
||||||
|
function renderSelectArea(info: SyncTargetInfo) {
|
||||||
|
if (info.name === 'joplinCloud' && showJoplinCloudForm) {
|
||||||
|
return renderJoplinCloudLoginForm();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<SelectButton
|
||||||
|
level={ButtonLevel.Primary}
|
||||||
|
title={_('Select')}
|
||||||
|
onClick={() => onSelectButtonClick(info.name)}
|
||||||
|
disabled={joplinCloudLoginInProgress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSyncTarget(info: SyncTargetInfo) {
|
||||||
|
const key = `syncTarget_${info.name}`;
|
||||||
|
const height = info.name !== 'joplinCloud' ? descriptionHeight : null;
|
||||||
|
|
||||||
|
const logoImageName = logosImageNames[info.name];
|
||||||
|
const logoImageSrc = logoImageName ? `${bridge().buildDir()}/images/syncTargetLogos/${logoImageName}` : '';
|
||||||
|
const logo = logoImageSrc ? <SyncTargetLogo src={logoImageSrc}/> : null;
|
||||||
|
const descriptionComp = <SyncTargetDescription height={height} ref={info.name === 'joplinCloud' ? joplinCloudDescriptionRef : null}>{info.description}</SyncTargetDescription>;
|
||||||
|
const featuresComp = showJoplinCloudForm && info.name === 'joplinCloud' ? null : renderFeatures(info.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SyncTargetBox id={key} key={key} faded={showJoplinCloudForm && info.name !== 'joplinCloud'}>
|
||||||
|
<SyncTargetTitle>{logo}{info.label}</SyncTargetTitle>
|
||||||
|
{descriptionComp}
|
||||||
|
{featuresComp}
|
||||||
|
{renderSelectArea(info)}
|
||||||
|
</SyncTargetBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelfHostingClick = useCallback(() => {
|
||||||
|
closeDialog(props.dispatch);
|
||||||
|
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'Config',
|
||||||
|
props: {
|
||||||
|
defaultSection: 'sync',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [props.dispatch]);
|
||||||
|
|
||||||
|
function renderContent() {
|
||||||
|
const boxes: any[] = [];
|
||||||
|
|
||||||
|
for (const name of syncTargetNames) {
|
||||||
|
const info = SyncTargetRegistry.infoByName(name);
|
||||||
|
if (info.supportsSelfHosted) continue;
|
||||||
|
boxes.push(renderSyncTarget(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
const selfHostingMessage = showJoplinCloudForm ? null : <SelfHostingMessage>Self-hosting? Joplin also supports various self-hosting options such as Nextcloud, WebDAV, AWS S3 and Joplin Server. <a href="#" onClick={onSelfHostingClick}>Click here to select one</a>.</SelfHostingMessage>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentRoot>
|
||||||
|
<SyncTargetBoxes>
|
||||||
|
{boxes}
|
||||||
|
</SyncTargetBoxes>
|
||||||
|
{selfHostingMessage}
|
||||||
|
</ContentRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDialogWrapper() {
|
||||||
|
return (
|
||||||
|
<StyledRoot>
|
||||||
|
<DialogTitle title={_('Joplin can synchronise your notes using various providers. Select one from the list below.')} justifyContent="center"/>
|
||||||
|
{renderContent()}
|
||||||
|
<DialogButtonRow
|
||||||
|
themeId={props.themeId}
|
||||||
|
onClick={onButtonRowClick}
|
||||||
|
okButtonShow={false}
|
||||||
|
cancelButtonLabel={_('Close')}
|
||||||
|
/>
|
||||||
|
</StyledRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog renderContent={renderDialogWrapper}/>
|
||||||
|
);
|
||||||
|
}
|
@ -19,7 +19,7 @@ const { BaseScreenComponent } = require('../base-screen.js');
|
|||||||
const { Dropdown } = require('../Dropdown.js');
|
const { Dropdown } = require('../Dropdown.js');
|
||||||
const { themeStyle } = require('../global-style.js');
|
const { themeStyle } = require('../global-style.js');
|
||||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||||
const RNFS = require('react-native-fs');
|
const RNFS = require('react-native-fs');
|
||||||
|
|
||||||
class ConfigScreenComponent extends BaseScreenComponent {
|
class ConfigScreenComponent extends BaseScreenComponent {
|
||||||
|
@ -78,7 +78,7 @@ import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
|
|||||||
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
||||||
const { themeStyle } = require('./components/global-style.js');
|
const { themeStyle } = require('./components/global-style.js');
|
||||||
|
|
||||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js');
|
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||||
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
|
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
|
||||||
const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js');
|
const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js');
|
||||||
const SyncTargetWebDAV = require('@joplin/lib/SyncTargetWebDAV.js');
|
const SyncTargetWebDAV = require('@joplin/lib/SyncTargetWebDAV.js');
|
||||||
|
@ -30,7 +30,7 @@ const fs = require('fs-extra');
|
|||||||
import JoplinError from './JoplinError';
|
import JoplinError from './JoplinError';
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const syswidecas = require('./vendor/syswide-cas');
|
const syswidecas = require('./vendor/syswide-cas');
|
||||||
const SyncTargetRegistry = require('./SyncTargetRegistry.js');
|
import SyncTargetRegistry from './SyncTargetRegistry';
|
||||||
const SyncTargetFilesystem = require('./SyncTargetFilesystem.js');
|
const SyncTargetFilesystem = require('./SyncTargetFilesystem.js');
|
||||||
const SyncTargetNextcloud = require('./SyncTargetNextcloud.js');
|
const SyncTargetNextcloud = require('./SyncTargetNextcloud.js');
|
||||||
const SyncTargetWebDAV = require('./SyncTargetWebDAV.js');
|
const SyncTargetWebDAV = require('./SyncTargetWebDAV.js');
|
||||||
|
@ -25,6 +25,14 @@ export default class BaseSyncTarget {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static description(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static supportsSelfHosted(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public option(name: string, defaultValue: any = null) {
|
public option(name: string, defaultValue: any = null) {
|
||||||
return this.options_ && name in this.options_ ? this.options_[name] : defaultValue;
|
return this.options_ && name in this.options_ ? this.options_[name] : defaultValue;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@ class SyncTargetAmazonS3 extends BaseSyncTarget {
|
|||||||
return `${_('AWS S3')} (Beta)`;
|
return `${_('AWS S3')} (Beta)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static description() {
|
||||||
|
return 'A service offered by Amazon Web Services (AWS) that provides object storage through a web service interface.';
|
||||||
|
}
|
||||||
|
|
||||||
async isAuthenticated() {
|
async isAuthenticated() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,14 @@ class SyncTargetDropbox extends BaseSyncTarget {
|
|||||||
return _('Dropbox');
|
return _('Dropbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static description() {
|
||||||
|
return 'A file hosting service that offers cloud storage and file synchronization';
|
||||||
|
}
|
||||||
|
|
||||||
|
static supportsSelfHosted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
authRouteName() {
|
authRouteName() {
|
||||||
return 'DropboxLogin';
|
return 'DropboxLogin';
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,14 @@ export default class SyncTargetJoplinCloud extends BaseSyncTarget {
|
|||||||
return _('Joplin Cloud');
|
return _('Joplin Cloud');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static description() {
|
||||||
|
return _('Joplin\'s own sync service. Also gives access to Joplin-specific features such as publishing notes or collaborating on notebooks with others.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static supportsSelfHosted(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async isAuthenticated() {
|
public async isAuthenticated() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,10 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
|
|||||||
return 'joplinServer';
|
return 'joplinServer';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static description() {
|
||||||
|
return 'Besides synchronisation and improved performances, Joplin Server also gives access to Joplin-specific sharing features.';
|
||||||
|
}
|
||||||
|
|
||||||
public static label() {
|
public static label() {
|
||||||
return `${_('Joplin Server')} (Beta)`;
|
return `${_('Joplin Server')} (Beta)`;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ class SyncTargetNextcloud extends BaseSyncTarget {
|
|||||||
return _('Nextcloud');
|
return _('Nextcloud');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static description() {
|
||||||
|
return 'A suite of client-server software for creating and using file hosting services.';
|
||||||
|
}
|
||||||
|
|
||||||
async isAuthenticated() {
|
async isAuthenticated() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,14 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
|
|||||||
return _('OneDrive');
|
return _('OneDrive');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static description() {
|
||||||
|
return 'A file hosting service operated by Microsoft as part of its web version of Office.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static supportsSelfHosted(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
async isAuthenticated() {
|
async isAuthenticated() {
|
||||||
return !!this.api().auth();
|
return !!this.api().auth();
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,47 @@
|
|||||||
class SyncTargetRegistry {
|
export interface SyncTargetInfo {
|
||||||
static classById(syncTargetId) {
|
id: number;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
supportsSelfHosted: boolean;
|
||||||
|
supportsConfigCheck: boolean;
|
||||||
|
description: string;
|
||||||
|
classRef: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SyncTargetRegistry {
|
||||||
|
|
||||||
|
private static reg_: Record<number, SyncTargetInfo> = {};
|
||||||
|
|
||||||
|
public static classById(syncTargetId: number) {
|
||||||
const info = SyncTargetRegistry.reg_[syncTargetId];
|
const info = SyncTargetRegistry.reg_[syncTargetId];
|
||||||
if (!info) throw new Error(`Invalid id: ${syncTargetId}`);
|
if (!info) throw new Error(`Invalid id: ${syncTargetId}`);
|
||||||
return info.classRef;
|
return info.classRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
static addClass(SyncTargetClass) {
|
public static infoByName(name: string): SyncTargetInfo {
|
||||||
|
for (const [, info] of Object.entries(this.reg_)) {
|
||||||
|
if (info.name === name) return info;
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown name: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static addClass(SyncTargetClass: any) {
|
||||||
this.reg_[SyncTargetClass.id()] = {
|
this.reg_[SyncTargetClass.id()] = {
|
||||||
id: SyncTargetClass.id(),
|
id: SyncTargetClass.id(),
|
||||||
name: SyncTargetClass.targetName(),
|
name: SyncTargetClass.targetName(),
|
||||||
label: SyncTargetClass.label(),
|
label: SyncTargetClass.label(),
|
||||||
classRef: SyncTargetClass,
|
classRef: SyncTargetClass,
|
||||||
|
description: SyncTargetClass.description(),
|
||||||
|
supportsSelfHosted: SyncTargetClass.supportsSelfHosted(),
|
||||||
supportsConfigCheck: SyncTargetClass.supportsConfigCheck(),
|
supportsConfigCheck: SyncTargetClass.supportsConfigCheck(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static allIds() {
|
public static allIds() {
|
||||||
return Object.keys(this.reg_);
|
return Object.keys(this.reg_);
|
||||||
}
|
}
|
||||||
|
|
||||||
static nameToId(name) {
|
public static nameToId(name: string) {
|
||||||
for (const n in this.reg_) {
|
for (const n in this.reg_) {
|
||||||
if (!this.reg_.hasOwnProperty(n)) continue;
|
if (!this.reg_.hasOwnProperty(n)) continue;
|
||||||
if (this.reg_[n].name === name) return this.reg_[n].id;
|
if (this.reg_[n].name === name) return this.reg_[n].id;
|
||||||
@ -27,7 +49,7 @@ class SyncTargetRegistry {
|
|||||||
throw new Error(`Name not found: ${name}. Was the sync target registered?`);
|
throw new Error(`Name not found: ${name}. Was the sync target registered?`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static idToMetadata(id) {
|
public static idToMetadata(id: number) {
|
||||||
for (const n in this.reg_) {
|
for (const n in this.reg_) {
|
||||||
if (!this.reg_.hasOwnProperty(n)) continue;
|
if (!this.reg_.hasOwnProperty(n)) continue;
|
||||||
if (this.reg_[n].id === id) return this.reg_[n];
|
if (this.reg_[n].id === id) return this.reg_[n];
|
||||||
@ -35,12 +57,12 @@ class SyncTargetRegistry {
|
|||||||
throw new Error(`ID not found: ${id}`);
|
throw new Error(`ID not found: ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static idToName(id) {
|
public static idToName(id: number) {
|
||||||
return this.idToMetadata(id).name;
|
return this.idToMetadata(id).name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static idAndLabelPlainObject(os) {
|
public static idAndLabelPlainObject(os: string) {
|
||||||
const output = {};
|
const output: Record<string, string> = {};
|
||||||
for (const n in this.reg_) {
|
for (const n in this.reg_) {
|
||||||
if (!this.reg_.hasOwnProperty(n)) continue;
|
if (!this.reg_.hasOwnProperty(n)) continue;
|
||||||
const info = this.reg_[n];
|
const info = this.reg_[n];
|
||||||
@ -52,7 +74,3 @@ class SyncTargetRegistry {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncTargetRegistry.reg_ = {};
|
|
||||||
|
|
||||||
module.exports = SyncTargetRegistry;
|
|
@ -23,6 +23,10 @@ class SyncTargetWebDAV extends BaseSyncTarget {
|
|||||||
return _('WebDAV');
|
return _('WebDAV');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static description() {
|
||||||
|
return 'The WebDAV protocol allows users to create, change and move documents on a server. There are many WebDAV compatible servers, including SeaFile, Nginx or Apache.';
|
||||||
|
}
|
||||||
|
|
||||||
async isAuthenticated() {
|
async isAuthenticated() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const Setting = require('../../models/Setting').default;
|
const Setting = require('../../models/Setting').default;
|
||||||
const SyncTargetRegistry = require('../../SyncTargetRegistry');
|
const SyncTargetRegistry = require('../../SyncTargetRegistry').default;
|
||||||
const ObjectUtils = require('../../ObjectUtils');
|
const ObjectUtils = require('../../ObjectUtils');
|
||||||
const { _ } = require('../../locale');
|
const { _ } = require('../../locale');
|
||||||
const { createSelector } = require('reselect');
|
const { createSelector } = require('reselect');
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const shim = require('../../shim').default;
|
const shim = require('../../shim').default;
|
||||||
const SyncTargetRegistry = require('../../SyncTargetRegistry');
|
const SyncTargetRegistry = require('../../SyncTargetRegistry').default;
|
||||||
const { reg } = require('../../registry.js');
|
const { reg } = require('../../registry.js');
|
||||||
const { _ } = require('../../locale');
|
const { _ } = require('../../locale');
|
||||||
const Setting = require('../../models/Setting').default;
|
const Setting = require('../../models/Setting').default;
|
||||||
|
38
packages/lib/hooks/useElementSize.ts
Normal file
38
packages/lib/hooks/useElementSize.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import shim from '../shim';
|
||||||
|
const { useCallback, useEffect, useState } = shim.react();
|
||||||
|
import useEventListener from './useEventListener';
|
||||||
|
|
||||||
|
interface Size {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useElementSize(elementRef: any): Size {
|
||||||
|
const [size, setSize] = useState({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent too many rendering using useCallback
|
||||||
|
const updateSize = useCallback(() => {
|
||||||
|
const node = elementRef?.current;
|
||||||
|
if (node) {
|
||||||
|
setSize({
|
||||||
|
width: node.offsetWidth || 0,
|
||||||
|
height: node.offsetHeight || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [elementRef]);
|
||||||
|
|
||||||
|
// Initial size on mount
|
||||||
|
useEffect(() => {
|
||||||
|
updateSize();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEventListener('resize', updateSize);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useElementSize;
|
41
packages/lib/hooks/useEventListener.ts
Normal file
41
packages/lib/hooks/useEventListener.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import shim from '../shim';
|
||||||
|
const { useEffect, useRef } = shim.react();
|
||||||
|
|
||||||
|
function useEventListener(
|
||||||
|
eventName: any,
|
||||||
|
handler: any,
|
||||||
|
element?: any
|
||||||
|
) {
|
||||||
|
// Create a ref that stores handler
|
||||||
|
const savedHandler = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Define the listening target
|
||||||
|
const targetElement = element?.current || window;
|
||||||
|
if (!(targetElement && targetElement.addEventListener)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update saved handler if necessary
|
||||||
|
if (savedHandler.current !== handler) {
|
||||||
|
savedHandler.current = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create event listener that calls handler function stored in ref
|
||||||
|
const eventListener = (event: Event) => {
|
||||||
|
// eslint-disable-next-line no-extra-boolean-cast
|
||||||
|
if (!!savedHandler?.current) {
|
||||||
|
savedHandler.current(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
targetElement.addEventListener(eventName, eventListener);
|
||||||
|
|
||||||
|
// Remove event listener on cleanup
|
||||||
|
return () => {
|
||||||
|
targetElement.removeEventListener(eventName, eventListener);
|
||||||
|
};
|
||||||
|
}, [eventName, element, handler]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useEventListener;
|
@ -3,7 +3,7 @@ import { _, supportedLocalesToLanguages, defaultLocale } from '../locale';
|
|||||||
import eventManager from '../eventManager';
|
import eventManager from '../eventManager';
|
||||||
import BaseModel from '../BaseModel';
|
import BaseModel from '../BaseModel';
|
||||||
import Database from '../database';
|
import Database from '../database';
|
||||||
const SyncTargetRegistry = require('../SyncTargetRegistry.js');
|
import SyncTargetRegistry from '../SyncTargetRegistry';
|
||||||
import time from '../time';
|
import time from '../time';
|
||||||
import FileHandler, { SettingValues } from './settings/FileHandler';
|
import FileHandler, { SettingValues } from './settings/FileHandler';
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
@ -55,6 +55,7 @@ export interface SettingItem {
|
|||||||
needRestart?: boolean;
|
needRestart?: boolean;
|
||||||
autoSave?: boolean;
|
autoSave?: boolean;
|
||||||
storage?: SettingStorage;
|
storage?: SettingStorage;
|
||||||
|
hideLabel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingItems {
|
interface SettingItems {
|
||||||
@ -306,6 +307,17 @@ class Setting extends BaseModel {
|
|||||||
appTypes: [AppType.Desktop],
|
appTypes: [AppType.Desktop],
|
||||||
storage: SettingStorage.File,
|
storage: SettingStorage.File,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'sync.openSyncWizard': {
|
||||||
|
value: null,
|
||||||
|
type: SettingItemType.Button,
|
||||||
|
public: true,
|
||||||
|
appTypes: [AppType.Desktop],
|
||||||
|
label: () => _('Open Sync Wizard...'),
|
||||||
|
hideLabel: true,
|
||||||
|
section: 'sync',
|
||||||
|
},
|
||||||
|
|
||||||
'sync.target': {
|
'sync.target': {
|
||||||
value: SyncTargetRegistry.nameToId('dropbox'),
|
value: SyncTargetRegistry.nameToId('dropbox'),
|
||||||
type: SettingItemType.Int,
|
type: SettingItemType.Int,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
import Setting from './models/Setting';
|
import Setting from './models/Setting';
|
||||||
import shim from './shim';
|
import shim from './shim';
|
||||||
const SyncTargetRegistry = require('./SyncTargetRegistry.js');
|
import SyncTargetRegistry from './SyncTargetRegistry';
|
||||||
|
|
||||||
class Registry {
|
class Registry {
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { SqlQuery } from '../../database';
|
|||||||
import JoplinDatabase from '../../JoplinDatabase';
|
import JoplinDatabase from '../../JoplinDatabase';
|
||||||
import BaseItem from '../../models/BaseItem';
|
import BaseItem from '../../models/BaseItem';
|
||||||
import Setting from '../../models/Setting';
|
import Setting from '../../models/Setting';
|
||||||
const SyncTargetRegistry = require('../../SyncTargetRegistry');
|
import SyncTargetRegistry from '../../SyncTargetRegistry';
|
||||||
|
|
||||||
async function clearSyncContext() {
|
async function clearSyncContext() {
|
||||||
const syncTargetIds = SyncTargetRegistry.allIds();
|
const syncTargetIds = SyncTargetRegistry.allIds();
|
||||||
|
@ -35,7 +35,7 @@ const { FileApiDriverWebDav } = require('../file-api-driver-webdav.js');
|
|||||||
const { FileApiDriverDropbox } = require('../file-api-driver-dropbox.js');
|
const { FileApiDriverDropbox } = require('../file-api-driver-dropbox.js');
|
||||||
const { FileApiDriverOneDrive } = require('../file-api-driver-onedrive.js');
|
const { FileApiDriverOneDrive } = require('../file-api-driver-onedrive.js');
|
||||||
const { FileApiDriverAmazonS3 } = require('../file-api-driver-amazon-s3.js');
|
const { FileApiDriverAmazonS3 } = require('../file-api-driver-amazon-s3.js');
|
||||||
const SyncTargetRegistry = require('../SyncTargetRegistry.js');
|
import SyncTargetRegistry from '../SyncTargetRegistry';
|
||||||
const SyncTargetMemory = require('../SyncTargetMemory.js');
|
const SyncTargetMemory = require('../SyncTargetMemory.js');
|
||||||
const SyncTargetFilesystem = require('../SyncTargetFilesystem.js');
|
const SyncTargetFilesystem = require('../SyncTargetFilesystem.js');
|
||||||
const SyncTargetNextcloud = require('../SyncTargetNextcloud.js');
|
const SyncTargetNextcloud = require('../SyncTargetNextcloud.js');
|
||||||
|
Reference in New Issue
Block a user