1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +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"
msgstr ""
msgid "Documentation"
msgid "Website and documentation"
msgstr ""
msgid "About Joplin"
msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
msgid "OK"
msgstr ""

View File

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

View File

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

View File

@ -29,7 +29,20 @@ class ElectronAppWrapper {
}
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({
pathname: path.join(__dirname, 'index.html'),
@ -42,6 +55,11 @@ class ElectronAppWrapper {
this.win_.on('closed', () => {
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() {

View File

@ -3,6 +3,7 @@ require('app-module-path').addPath(__dirname);
const { BaseApplication } = require('lib/BaseApplication');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const { Setting } = require('lib/models/setting.js');
const { shim } = require('lib/shim.js');
const { BaseModel } = require('lib/base-model.js');
const { _, setLocale } = require('lib/locale.js');
const os = require('os');
@ -30,6 +31,7 @@ const appDefaultState = Object.assign({}, defaultState, {
fileToImport: null,
windowCommand: null,
noteVisiblePanes: ['editor', 'viewer'],
windowContentSize: bridge().windowContentSize(),
});
class Application extends BaseApplication {
@ -231,7 +233,7 @@ class Application extends BaseApplication {
}, {
label: _('Help'),
submenu: [{
label: _('Documentation'),
label: _('Website and documentation'),
accelerator: 'F1',
click () { bridge().openExternal('http://joplin.cozic.net') }
}, {
@ -242,7 +244,7 @@ class Application extends BaseApplication {
p.description,
'',
'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({
message: message.join('\n'),
@ -278,6 +280,16 @@ class Application extends BaseApplication {
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
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
@ -297,13 +309,17 @@ class Application extends BaseApplication {
id: Setting.value('activeFolderId'),
});
const runAutoUpdateCheck = function() {
bridge().checkForUpdatesAndNotify(Setting.value('profileDir') + '/log-autoupdater.txt');
// Note: Auto-update currently doesn't work in Linux: it downloads the update
// 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);
// For those who leave the app always open
setInterval(() => { runAutoUpdateCheck() }, 2 * 60 * 60 * 1000);
}
setTimeout(() => { runAutoUpdateCheck() }, 5000);
// For those who leave the app always open
setInterval(() => { runAutoUpdateCheck() }, 2 * 60 * 60 * 1000);
}
}

View File

@ -25,6 +25,17 @@ class Bridge {
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) {
const {dialog} = require('electron');
return dialog.showOpenDialog(options);

View File

@ -113,7 +113,9 @@ class NoteListComponent extends React.Component {
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
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}
<a
data-id={item.id}

View File

@ -115,9 +115,11 @@ class NoteListComponent extends React.Component {
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
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(
'div',
{ key: item.id, style: style },
{ key: item.id + '_' + item.todo_completed, style: style },
checkbox,
React.createElement(
'a',

View File

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

View File

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

View File

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

View File

@ -10,86 +10,98 @@ shim.isReactNative = () => {
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
shim.isElectron = () => {
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return 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) {
return 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) {
return true;
}
return false;
return false;
}
// Node requests can go wrong is so many different ways and with so
// many different error messages... This handler inspects the error
// and decides whether the request can safely be repeated or not.
function fetchRequestCanBeRetried(error) {
if (!error) return false;
if (!error) return false;
// 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
if (error.message == 'Network request failed') return true;
// 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
if (error.message == 'Network request failed') return true;
// request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up"
if (error.code == 'ECONNRESET') return true;
// request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up"
if (error.code == 'ECONNRESET') return true;
// OneDrive (or Node?) sometimes sends back a "not found" error for resources
// that definitely exist and in this case repeating the request works.
// Error is:
// 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;
// OneDrive (or Node?) sometimes sends back a "not found" error for resources
// that definitely exist and in this case repeating the request works.
// Error is:
// 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;
// network timeout at: https://public-ch3302...859f9b0e3ab.md
if (error.message && error.message.indexOf('network timeout') === 0) return true;
// network timeout at: https://public-ch3302...859f9b0e3ab.md
if (error.message && error.message.indexOf('network timeout') === 0) return true;
// name: 'FetchError',
// message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443',
// type: 'system',
// 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
//
// It's a Microsoft error: "A temporary failure in name resolution occurred."
if (error.code == 'EAI_AGAIN') return true;
// name: 'FetchError',
// message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443',
// type: 'system',
// 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
//
// It's a Microsoft error: "A temporary failure in name resolution occurred."
if (error.code == 'EAI_AGAIN') return true;
// request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443
// Code: ETIMEDOUT
if (error.code === 'ETIMEDOUT') return true;
// request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443
// Code: ETIMEDOUT
if (error.code === 'ETIMEDOUT') return true;
return false;
return false;
}
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.timeout) options.timeout = 1000 * 120; // ms
if (!('maxRetry' in options)) options.maxRetry = 5;
if (!options) options = {};
if (!options.timeout) options.timeout = 1000 * 120; // ms
if (!('maxRetry' in options)) options.maxRetry = 5;
let retryCount = 0;
while (true) {
try {
const response = await fetchFn();
return response;
} catch (error) {
if (fetchRequestCanBeRetried(error)) {
retryCount++;
if (retryCount > options.maxRetry) throw error;
await time.sleep(retryCount * 3);
} else {
throw error;
}
}
}
let retryCount = 0;
while (true) {
try {
const response = await fetchFn();
return response;
} catch (error) {
if (fetchRequestCanBeRetried(error)) {
retryCount++;
if (retryCount > options.maxRetry) throw error;
await time.sleep(retryCount * 3);
} else {
throw error;
}
}
}
}
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",
"version": "0.10.0",
"version": "0.10.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1168,6 +1168,11 @@
"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": {
"version": "0.4.2",
"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": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",

View File

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

View File

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

View File

@ -10,86 +10,98 @@ shim.isReactNative = () => {
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
shim.isElectron = () => {
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return 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) {
return 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) {
return true;
}
return false;
return false;
}
// Node requests can go wrong is so many different ways and with so
// many different error messages... This handler inspects the error
// and decides whether the request can safely be repeated or not.
function fetchRequestCanBeRetried(error) {
if (!error) return false;
if (!error) return false;
// 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
if (error.message == 'Network request failed') return true;
// 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
if (error.message == 'Network request failed') return true;
// request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up"
if (error.code == 'ECONNRESET') return true;
// request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up"
if (error.code == 'ECONNRESET') return true;
// OneDrive (or Node?) sometimes sends back a "not found" error for resources
// that definitely exist and in this case repeating the request works.
// Error is:
// 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;
// OneDrive (or Node?) sometimes sends back a "not found" error for resources
// that definitely exist and in this case repeating the request works.
// Error is:
// 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;
// network timeout at: https://public-ch3302...859f9b0e3ab.md
if (error.message && error.message.indexOf('network timeout') === 0) return true;
// network timeout at: https://public-ch3302...859f9b0e3ab.md
if (error.message && error.message.indexOf('network timeout') === 0) return true;
// name: 'FetchError',
// message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443',
// type: 'system',
// 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
//
// It's a Microsoft error: "A temporary failure in name resolution occurred."
if (error.code == 'EAI_AGAIN') return true;
// name: 'FetchError',
// message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443',
// type: 'system',
// 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
//
// It's a Microsoft error: "A temporary failure in name resolution occurred."
if (error.code == 'EAI_AGAIN') return true;
// request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443
// Code: ETIMEDOUT
if (error.code === 'ETIMEDOUT') return true;
// request to https://public-...8fd8bc6bb68e9c4d17a.md failed, reason: connect ETIMEDOUT 204.79.197.213:443
// Code: ETIMEDOUT
if (error.code === 'ETIMEDOUT') return true;
return false;
return false;
}
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.timeout) options.timeout = 1000 * 120; // ms
if (!('maxRetry' in options)) options.maxRetry = 5;
if (!options) options = {};
if (!options.timeout) options.timeout = 1000 * 120; // ms
if (!('maxRetry' in options)) options.maxRetry = 5;
let retryCount = 0;
while (true) {
try {
const response = await fetchFn();
return response;
} catch (error) {
if (fetchRequestCanBeRetried(error)) {
retryCount++;
if (retryCount > options.maxRetry) throw error;
await time.sleep(retryCount * 3);
} else {
throw error;
}
}
}
let retryCount = 0;
while (true) {
try {
const response = await fetchFn();
return response;
} catch (error) {
if (fetchRequestCanBeRetried(error)) {
retryCount++;
if (retryCount > options.maxRetry) throw error;
await time.sleep(retryCount * 3);
} else {
throw error;
}
}
}
}
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