1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Save window state

This commit is contained in:
Laurent Cozic 2017-11-14 18:02:58 +00:00
parent bf916d5b9b
commit dbb5599b0f
19 changed files with 275 additions and 143 deletions

View File

@ -499,12 +499,16 @@ msgstr ""
msgid "Help" msgid "Help"
msgstr "" msgstr ""
msgid "Documentation" msgid "Website and documentation"
msgstr "" msgstr ""
msgid "About Joplin" msgid "About Joplin"
msgstr "" msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
msgid "OK" msgid "OK"
msgstr "" msgstr ""

View File

@ -550,12 +550,17 @@ msgstr "Options"
msgid "Help" msgid "Help"
msgstr "Aide" msgstr "Aide"
msgid "Documentation" #, fuzzy
msgid "Website and documentation"
msgstr "Documentation" msgstr "Documentation"
msgid "About Joplin" msgid "About Joplin"
msgstr "A props de Joplin" msgstr "A props de Joplin"
#, fuzzy, javascript-format
msgid "%s %s (%s, %s)"
msgstr "%s %s (%s)"
msgid "OK" msgid "OK"
msgstr "OK" msgstr "OK"

View File

@ -499,12 +499,16 @@ msgstr ""
msgid "Help" msgid "Help"
msgstr "" msgstr ""
msgid "Documentation" msgid "Website and documentation"
msgstr "" msgstr ""
msgid "About Joplin" msgid "About Joplin"
msgstr "" msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
msgid "OK" msgid "OK"
msgstr "" msgstr ""

View File

@ -29,7 +29,20 @@ class ElectronAppWrapper {
} }
createWindow() { createWindow() {
this.win_ = new BrowserWindow({width: 800, height: 600}) const windowStateKeeper = require('electron-window-state');
// Load the previous state with fallback to defaults
const windowState = windowStateKeeper({
defaultWidth: 800,
defaultHeight: 600,
});
this.win_ = new BrowserWindow({
'x': windowState.x,
'y': windowState.y,
'width': windowState.width,
'height': windowState.height
})
this.win_.loadURL(url.format({ this.win_.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'), pathname: path.join(__dirname, 'index.html'),
@ -42,6 +55,11 @@ class ElectronAppWrapper {
this.win_.on('closed', () => { this.win_.on('closed', () => {
this.win_ = null this.win_ = null
}) })
// Let us register listeners on the window, so we can update the state
// automatically (the listeners will be removed when the window is closed)
// and restore the maximized or full screen state
windowState.manage(this.win_);
} }
async waitForElectronAppReady() { async waitForElectronAppReady() {

View File

@ -3,6 +3,7 @@ require('app-module-path').addPath(__dirname);
const { BaseApplication } = require('lib/BaseApplication'); const { BaseApplication } = require('lib/BaseApplication');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const { Setting } = require('lib/models/setting.js'); const { Setting } = require('lib/models/setting.js');
const { shim } = require('lib/shim.js');
const { BaseModel } = require('lib/base-model.js'); const { BaseModel } = require('lib/base-model.js');
const { _, setLocale } = require('lib/locale.js'); const { _, setLocale } = require('lib/locale.js');
const os = require('os'); const os = require('os');
@ -30,6 +31,7 @@ const appDefaultState = Object.assign({}, defaultState, {
fileToImport: null, fileToImport: null,
windowCommand: null, windowCommand: null,
noteVisiblePanes: ['editor', 'viewer'], noteVisiblePanes: ['editor', 'viewer'],
windowContentSize: bridge().windowContentSize(),
}); });
class Application extends BaseApplication { class Application extends BaseApplication {
@ -231,7 +233,7 @@ class Application extends BaseApplication {
}, { }, {
label: _('Help'), label: _('Help'),
submenu: [{ submenu: [{
label: _('Documentation'), label: _('Website and documentation'),
accelerator: 'F1', accelerator: 'F1',
click () { bridge().openExternal('http://joplin.cozic.net') } click () { bridge().openExternal('http://joplin.cozic.net') }
}, { }, {
@ -242,7 +244,7 @@ class Application extends BaseApplication {
p.description, p.description,
'', '',
'Copyright © 2016-2017', 'Copyright © 2016-2017',
_('%s %s (%s)', p.name, p.version, Setting.value('env')), _('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform),
]; ];
bridge().showMessageBox({ bridge().showMessageBox({
message: message.join('\n'), message: message.join('\n'),
@ -278,6 +280,16 @@ class Application extends BaseApplication {
this.initRedux(); this.initRedux();
// const windowSize = Setting.value('windowSize');
// const width = windowSize && windowSize.width ? windowSize.width : 800;
// const height = windowSize && windowSize.height ? windowSize.height : 800;
// bridge().windowSetSize(width, height);
// this.store().dispatch({
// type: 'WINDOW_CONTENT_SIZE_SET',
// size: bridge().windowContentSize(),
// });
// Since the settings need to be loaded before the store is created, it will never // Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be // receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update. // initialised. So we manually call dispatchUpdateAll() to force an update.
@ -297,13 +309,17 @@ class Application extends BaseApplication {
id: Setting.value('activeFolderId'), id: Setting.value('activeFolderId'),
}); });
const runAutoUpdateCheck = function() { // Note: Auto-update currently doesn't work in Linux: it downloads the update
bridge().checkForUpdatesAndNotify(Setting.value('profileDir') + '/log-autoupdater.txt'); // but then doesn't install it on exit.
} if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = function() {
bridge().checkForUpdatesAndNotify(Setting.value('profileDir') + '/log-autoupdater.txt');
}
setTimeout(() => { runAutoUpdateCheck() }, 5000); setTimeout(() => { runAutoUpdateCheck() }, 5000);
// For those who leave the app always open // For those who leave the app always open
setInterval(() => { runAutoUpdateCheck() }, 2 * 60 * 60 * 1000); setInterval(() => { runAutoUpdateCheck() }, 2 * 60 * 60 * 1000);
}
} }
} }

View File

@ -25,6 +25,17 @@ class Bridge {
return { width: s[0], height: s[1] }; return { width: s[0], height: s[1] };
} }
windowSize() {
if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getSize();
return { width: s[0], height: s[1] };
}
windowSetSize(width, height) {
if (!this.window()) return;
return this.window().setSize(width, height);
}
showOpenDialog(options) { showOpenDialog(options) {
const {dialog} = require('electron'); const {dialog} = require('electron');
return dialog.showOpenDialog(options); return dialog.showOpenDialog(options);

View File

@ -113,7 +113,9 @@ class NoteListComponent extends React.Component {
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4; listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, this.style().listItemTitleCompleted); if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, this.style().listItemTitleCompleted);
return <div key={item.id} style={style}> // Need to include "todo_completed" in key so that checkbox is updated when
// item is changed via sync.
return <div key={item.id + '_' + item.todo_completed} style={style}>
{checkbox} {checkbox}
<a <a
data-id={item.id} data-id={item.id}

View File

@ -115,9 +115,11 @@ class NoteListComponent extends React.Component {
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4; listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, this.style().listItemTitleCompleted); if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, this.style().listItemTitleCompleted);
// Need to include "todo_completed" in key so that checkbox is updated when
// item is changed via sync.
return React.createElement( return React.createElement(
'div', 'div',
{ key: item.id, style: style }, { key: item.id + '_' + item.todo_completed, style: style },
checkbox, checkbox,
React.createElement( React.createElement(
'a', 'a',

View File

@ -4,6 +4,7 @@ const { createStore } = require('redux');
const { connect, Provider } = require('react-redux'); const { connect, Provider } = require('react-redux');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const { Setting } = require('lib/models/setting.js');
const { MainScreen } = require('./MainScreen.min.js'); const { MainScreen } = require('./MainScreen.min.js');
const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js'); const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
@ -30,11 +31,6 @@ async function initialize(dispatch) {
}, 10); }, 10);
}); });
store.dispatch({
type: 'WINDOW_CONTENT_SIZE_SET',
size: bridge().windowContentSize(),
});
store.dispatch({ store.dispatch({
type: 'NOTE_VISIBLE_PANES_SET', type: 'NOTE_VISIBLE_PANES_SET',
panes: Setting.value('noteVisiblePanes'), panes: Setting.value('noteVisiblePanes'),

View File

@ -4,6 +4,7 @@ const { createStore } = require('redux');
const { connect, Provider } = require('react-redux'); const { connect, Provider } = require('react-redux');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const { Setting } = require('lib/models/setting.js');
const { MainScreen } = require('./MainScreen.min.js'); const { MainScreen } = require('./MainScreen.min.js');
const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js'); const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
@ -30,11 +31,6 @@ async function initialize(dispatch) {
}, 10); }, 10);
}); });
store.dispatch({
type: 'WINDOW_CONTENT_SIZE_SET',
size: bridge().windowContentSize()
});
store.dispatch({ store.dispatch({
type: 'NOTE_VISIBLE_PANES_SET', type: 'NOTE_VISIBLE_PANES_SET',
panes: Setting.value('noteVisiblePanes') panes: Setting.value('noteVisiblePanes')

View File

@ -110,7 +110,7 @@ class Setting extends BaseModel {
this.logger().info('Setting: ' + key + ' = ' + c.value + ' => ' + value); this.logger().info('Setting: ' + key + ' = ' + c.value + ' => ' + value);
c.value = this.formatValue(key, value); c.value = value;
this.dispatch({ this.dispatch({
type: 'SETTING_UPDATE_ONE', type: 'SETTING_UPDATE_ONE',
@ -143,6 +143,7 @@ class Setting extends BaseModel {
if (md.type == Setting.TYPE_INT) return value.toFixed(0); if (md.type == Setting.TYPE_INT) return value.toFixed(0);
if (md.type == Setting.TYPE_BOOL) return value ? '1' : '0'; if (md.type == Setting.TYPE_BOOL) return value ? '1' : '0';
if (md.type == Setting.TYPE_ARRAY) return value ? JSON.stringify(value) : '[]'; if (md.type == Setting.TYPE_ARRAY) return value ? JSON.stringify(value) : '[]';
if (md.type == Setting.TYPE_OBJECT) return value ? JSON.stringify(value) : '{}';
return value; return value;
} }
@ -162,11 +163,19 @@ class Setting extends BaseModel {
} }
if (md.type === Setting.TYPE_ARRAY) { if (md.type === Setting.TYPE_ARRAY) {
if (!value) return [];
if (Array.isArray(value)) return value; if (Array.isArray(value)) return value;
if (typeof value === 'string') return JSON.parse(value); if (typeof value === 'string') return JSON.parse(value);
return []; return [];
} }
if (md.type === Setting.TYPE_OBJECT) {
if (!value) return {};
if (typeof value === 'object') return value;
if (typeof value === 'string') return JSON.parse(value);
return {};
}
return value; return value;
} }
@ -308,6 +317,7 @@ class Setting extends BaseModel {
if (typeId === Setting.TYPE_STRING) return 'string'; if (typeId === Setting.TYPE_STRING) return 'string';
if (typeId === Setting.TYPE_BOOL) return 'bool'; if (typeId === Setting.TYPE_BOOL) return 'bool';
if (typeId === Setting.TYPE_ARRAY) return 'array'; if (typeId === Setting.TYPE_ARRAY) return 'array';
if (typeId === Setting.TYPE_OBJECT) return 'object';
} }
} }
@ -320,6 +330,7 @@ Setting.TYPE_INT = 1;
Setting.TYPE_STRING = 2; Setting.TYPE_STRING = 2;
Setting.TYPE_BOOL = 3; Setting.TYPE_BOOL = 3;
Setting.TYPE_ARRAY = 4; Setting.TYPE_ARRAY = 4;
Setting.TYPE_OBJECT = 5;
Setting.THEME_LIGHT = 1; Setting.THEME_LIGHT = 1;
Setting.THEME_DARK = 2; Setting.THEME_DARK = 2;

View File

@ -10,86 +10,98 @@ shim.isReactNative = () => {
return !shim.isNode(); return !shim.isNode();
}; };
shim.isLinux = () => {
return process && process.platform === 'linux';
}
shim.isWindows = () => {
return process && process.platform === 'win32';
}
shim.isMac = () => {
return process && process.platform === 'darwin';
}
// https://github.com/cheton/is-electron // https://github.com/cheton/is-electron
shim.isElectron = () => { shim.isElectron = () => {
// Renderer process // Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') { if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true; return true;
} }
// Main process // Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) { if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true; return true;
} }
// Detect the user agent when the `nodeIntegration` option is set to true // Detect the user agent when the `nodeIntegration` option is set to true
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
return true; return true;
} }
return false; return false;
} }
// Node requests can go wrong is so many different ways and with so // Node requests can go wrong is so many different ways and with so
// many different error messages... This handler inspects the error // many different error messages... This handler inspects the error
// and decides whether the request can safely be repeated or not. // and decides whether the request can safely be repeated or not.
function fetchRequestCanBeRetried(error) { function fetchRequestCanBeRetried(error) {
if (!error) return false; if (!error) return false;
// Unfortunately the error 'Network request failed' doesn't have a type // Unfortunately the error 'Network request failed' doesn't have a type
// or error code, so hopefully that message won't change and is not localized // or error code, so hopefully that message won't change and is not localized
if (error.message == 'Network request failed') return true; if (error.message == 'Network request failed') return true;
// request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up" // request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up"
if (error.code == 'ECONNRESET') return true; if (error.code == 'ECONNRESET') return true;
// OneDrive (or Node?) sometimes sends back a "not found" error for resources // OneDrive (or Node?) sometimes sends back a "not found" error for resources
// that definitely exist and in this case repeating the request works. // that definitely exist and in this case repeating the request works.
// Error is: // Error is:
// request to https://graph.microsoft.com/v1.0/drive/special/approot failed, reason: getaddrinfo ENOTFOUND graph.microsoft.com graph.microsoft.com:443 // request to https://graph.microsoft.com/v1.0/drive/special/approot failed, reason: getaddrinfo ENOTFOUND graph.microsoft.com graph.microsoft.com:443
if (error.code == 'ENOTFOUND') return true; if (error.code == 'ENOTFOUND') return true;
// network timeout at: https://public-ch3302...859f9b0e3ab.md // network timeout at: https://public-ch3302...859f9b0e3ab.md
if (error.message && error.message.indexOf('network timeout') === 0) return true; if (error.message && error.message.indexOf('network timeout') === 0) return true;
// name: 'FetchError', // name: 'FetchError',
// message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443', // message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443',
// type: 'system', // type: 'system',
// errno: 'EAI_AGAIN', // errno: 'EAI_AGAIN',
// code: 'EAI_AGAIN' } } reason: { FetchError: request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443 // code: 'EAI_AGAIN' } } reason: { FetchError: request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443
// //
// It's a Microsoft error: "A temporary failure in name resolution occurred." // It's a Microsoft error: "A temporary failure in name resolution occurred."
if (error.code == 'EAI_AGAIN') return true; if (error.code == 'EAI_AGAIN') return true;
// request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443 // request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443
// Code: ETIMEDOUT // Code: ETIMEDOUT
if (error.code === 'ETIMEDOUT') return true; if (error.code === 'ETIMEDOUT') return true;
return false; return false;
} }
shim.fetchWithRetry = async function(fetchFn, options = null) { shim.fetchWithRetry = async function(fetchFn, options = null) {
const { time } = require('lib/time-utils.js'); const { time } = require('lib/time-utils.js');
if (!options) options = {}; if (!options) options = {};
if (!options.timeout) options.timeout = 1000 * 120; // ms if (!options.timeout) options.timeout = 1000 * 120; // ms
if (!('maxRetry' in options)) options.maxRetry = 5; if (!('maxRetry' in options)) options.maxRetry = 5;
let retryCount = 0; let retryCount = 0;
while (true) { while (true) {
try { try {
const response = await fetchFn(); const response = await fetchFn();
return response; return response;
} catch (error) { } catch (error) {
if (fetchRequestCanBeRetried(error)) { if (fetchRequestCanBeRetried(error)) {
retryCount++; retryCount++;
if (retryCount > options.maxRetry) throw error; if (retryCount > options.maxRetry) throw error;
await time.sleep(retryCount * 3); await time.sleep(retryCount * 3);
} else { } else {
throw error; throw error;
} }
} }
} }
} }
shim.nativeFetch_ = typeof fetch !== 'undefined' ? fetch : null; shim.nativeFetch_ = typeof fetch !== 'undefined' ? fetch : null;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "Joplin", "name": "Joplin",
"version": "0.10.0", "version": "0.10.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1168,6 +1168,11 @@
"mimic-response": "1.0.0" "mimic-response": "1.0.0"
} }
}, },
"deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
},
"deep-extend": { "deep-extend": {
"version": "0.4.2", "version": "0.4.2",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
@ -1612,6 +1617,26 @@
} }
} }
}, },
"electron-window-state": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-4.1.1.tgz",
"integrity": "sha1-azT9wxs4UU3+yLfI97XUrdtnYy0=",
"requires": {
"deep-equal": "1.0.1",
"jsonfile": "2.4.0",
"mkdirp": "0.5.1"
},
"dependencies": {
"jsonfile": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"requires": {
"graceful-fs": "4.1.11"
}
}
}
},
"encoding": { "encoding": {
"version": "0.1.12", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",

View File

@ -43,6 +43,7 @@
"electron-context-menu": "^0.9.1", "electron-context-menu": "^0.9.1",
"electron-log": "^2.2.11", "electron-log": "^2.2.11",
"electron-updater": "^2.16.1", "electron-updater": "^2.16.1",
"electron-window-state": "^4.1.1",
"follow-redirects": "^1.2.5", "follow-redirects": "^1.2.5",
"form-data": "^2.3.1", "form-data": "^2.3.1",
"fs-extra": "^4.0.2", "fs-extra": "^4.0.2",

View File

@ -110,7 +110,7 @@ class Setting extends BaseModel {
this.logger().info('Setting: ' + key + ' = ' + c.value + ' => ' + value); this.logger().info('Setting: ' + key + ' = ' + c.value + ' => ' + value);
c.value = this.formatValue(key, value); c.value = value;
this.dispatch({ this.dispatch({
type: 'SETTING_UPDATE_ONE', type: 'SETTING_UPDATE_ONE',
@ -143,6 +143,7 @@ class Setting extends BaseModel {
if (md.type == Setting.TYPE_INT) return value.toFixed(0); if (md.type == Setting.TYPE_INT) return value.toFixed(0);
if (md.type == Setting.TYPE_BOOL) return value ? '1' : '0'; if (md.type == Setting.TYPE_BOOL) return value ? '1' : '0';
if (md.type == Setting.TYPE_ARRAY) return value ? JSON.stringify(value) : '[]'; if (md.type == Setting.TYPE_ARRAY) return value ? JSON.stringify(value) : '[]';
if (md.type == Setting.TYPE_OBJECT) return value ? JSON.stringify(value) : '{}';
return value; return value;
} }
@ -162,11 +163,19 @@ class Setting extends BaseModel {
} }
if (md.type === Setting.TYPE_ARRAY) { if (md.type === Setting.TYPE_ARRAY) {
if (!value) return [];
if (Array.isArray(value)) return value; if (Array.isArray(value)) return value;
if (typeof value === 'string') return JSON.parse(value); if (typeof value === 'string') return JSON.parse(value);
return []; return [];
} }
if (md.type === Setting.TYPE_OBJECT) {
if (!value) return {};
if (typeof value === 'object') return value;
if (typeof value === 'string') return JSON.parse(value);
return {};
}
return value; return value;
} }
@ -308,6 +317,7 @@ class Setting extends BaseModel {
if (typeId === Setting.TYPE_STRING) return 'string'; if (typeId === Setting.TYPE_STRING) return 'string';
if (typeId === Setting.TYPE_BOOL) return 'bool'; if (typeId === Setting.TYPE_BOOL) return 'bool';
if (typeId === Setting.TYPE_ARRAY) return 'array'; if (typeId === Setting.TYPE_ARRAY) return 'array';
if (typeId === Setting.TYPE_OBJECT) return 'object';
} }
} }
@ -320,6 +330,7 @@ Setting.TYPE_INT = 1;
Setting.TYPE_STRING = 2; Setting.TYPE_STRING = 2;
Setting.TYPE_BOOL = 3; Setting.TYPE_BOOL = 3;
Setting.TYPE_ARRAY = 4; Setting.TYPE_ARRAY = 4;
Setting.TYPE_OBJECT = 5;
Setting.THEME_LIGHT = 1; Setting.THEME_LIGHT = 1;
Setting.THEME_DARK = 2; Setting.THEME_DARK = 2;

View File

@ -10,86 +10,98 @@ shim.isReactNative = () => {
return !shim.isNode(); return !shim.isNode();
}; };
shim.isLinux = () => {
return process && process.platform === 'linux';
}
shim.isWindows = () => {
return process && process.platform === 'win32';
}
shim.isMac = () => {
return process && process.platform === 'darwin';
}
// https://github.com/cheton/is-electron // https://github.com/cheton/is-electron
shim.isElectron = () => { shim.isElectron = () => {
// Renderer process // Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') { if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true; return true;
} }
// Main process // Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) { if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true; return true;
} }
// Detect the user agent when the `nodeIntegration` option is set to true // Detect the user agent when the `nodeIntegration` option is set to true
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
return true; return true;
} }
return false; return false;
} }
// Node requests can go wrong is so many different ways and with so // Node requests can go wrong is so many different ways and with so
// many different error messages... This handler inspects the error // many different error messages... This handler inspects the error
// and decides whether the request can safely be repeated or not. // and decides whether the request can safely be repeated or not.
function fetchRequestCanBeRetried(error) { function fetchRequestCanBeRetried(error) {
if (!error) return false; if (!error) return false;
// Unfortunately the error 'Network request failed' doesn't have a type // Unfortunately the error 'Network request failed' doesn't have a type
// or error code, so hopefully that message won't change and is not localized // or error code, so hopefully that message won't change and is not localized
if (error.message == 'Network request failed') return true; if (error.message == 'Network request failed') return true;
// request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up" // request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up"
if (error.code == 'ECONNRESET') return true; if (error.code == 'ECONNRESET') return true;
// OneDrive (or Node?) sometimes sends back a "not found" error for resources // OneDrive (or Node?) sometimes sends back a "not found" error for resources
// that definitely exist and in this case repeating the request works. // that definitely exist and in this case repeating the request works.
// Error is: // Error is:
// request to https://graph.microsoft.com/v1.0/drive/special/approot failed, reason: getaddrinfo ENOTFOUND graph.microsoft.com graph.microsoft.com:443 // request to https://graph.microsoft.com/v1.0/drive/special/approot failed, reason: getaddrinfo ENOTFOUND graph.microsoft.com graph.microsoft.com:443
if (error.code == 'ENOTFOUND') return true; if (error.code == 'ENOTFOUND') return true;
// network timeout at: https://public-ch3302...859f9b0e3ab.md // network timeout at: https://public-ch3302...859f9b0e3ab.md
if (error.message && error.message.indexOf('network timeout') === 0) return true; if (error.message && error.message.indexOf('network timeout') === 0) return true;
// name: 'FetchError', // name: 'FetchError',
// message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443', // message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443',
// type: 'system', // type: 'system',
// errno: 'EAI_AGAIN', // errno: 'EAI_AGAIN',
// code: 'EAI_AGAIN' } } reason: { FetchError: request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443 // code: 'EAI_AGAIN' } } reason: { FetchError: request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443
// //
// It's a Microsoft error: "A temporary failure in name resolution occurred." // It's a Microsoft error: "A temporary failure in name resolution occurred."
if (error.code == 'EAI_AGAIN') return true; if (error.code == 'EAI_AGAIN') return true;
// request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443 // request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443
// Code: ETIMEDOUT // Code: ETIMEDOUT
if (error.code === 'ETIMEDOUT') return true; if (error.code === 'ETIMEDOUT') return true;
return false; return false;
} }
shim.fetchWithRetry = async function(fetchFn, options = null) { shim.fetchWithRetry = async function(fetchFn, options = null) {
const { time } = require('lib/time-utils.js'); const { time } = require('lib/time-utils.js');
if (!options) options = {}; if (!options) options = {};
if (!options.timeout) options.timeout = 1000 * 120; // ms if (!options.timeout) options.timeout = 1000 * 120; // ms
if (!('maxRetry' in options)) options.maxRetry = 5; if (!('maxRetry' in options)) options.maxRetry = 5;
let retryCount = 0; let retryCount = 0;
while (true) { while (true) {
try { try {
const response = await fetchFn(); const response = await fetchFn();
return response; return response;
} catch (error) { } catch (error) {
if (fetchRequestCanBeRetried(error)) { if (fetchRequestCanBeRetried(error)) {
retryCount++; retryCount++;
if (retryCount > options.maxRetry) throw error; if (retryCount > options.maxRetry) throw error;
await time.sleep(retryCount * 3); await time.sleep(retryCount * 3);
} else { } else {
throw error; throw error;
} }
} }
} }
} }
shim.nativeFetch_ = typeof fetch !== 'undefined' ? fetch : null; shim.nativeFetch_ = typeof fetch !== 'undefined' ? fetch : null;

6
linkToLocal.sh Normal file
View File

@ -0,0 +1,6 @@
#!/bin/bash
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$ROOT_DIR/CliClient/node_modules"
rm -rf tkwidgets
ln -s /mnt/d/Docs/PROGS/Node/tkwidgets/src tkwidgets