format js code with eslint

This commit is contained in:
Kesha Antonov
2021-12-05 21:27:10 +03:00
parent 2ededc9694
commit d157726679
10 changed files with 4404 additions and 2164 deletions

82
.eslintrc.js Normal file
View File

@@ -0,0 +1,82 @@
module.exports = {
env: {
es2020: true,
jest: true,
},
parser: '@babel/eslint-parser',
extends: [
'standard',
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 11,
sourceType: 'module',
},
plugins: [
'react',
'react-hooks',
],
settings: {
react: {
version: 'detect',
},
},
rules: {
indent: [
'error',
2, {
SwitchCase: 1,
ignoredNodes: [
'TemplateLiteral',
],
},
],
'template-curly-spacing': 'off',
'linebreak-style': [
'error',
'unix',
],
quotes: [
'error',
'single',
],
semi: [
'error',
'never',
],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'never',
functions: 'never',
},
],
'no-func-assign': 'off',
'no-class-assign': 'off',
'no-useless-escape': 'off',
curly: [2, 'multi', 'consistent'],
'react/prop-types': 'off', // TODO: TURN ON AND FIX ALL WARNINGS
'react/display-name': 'off',
},
globals: {
describe: 'readonly',
test: 'readonly',
jest: 'readonly',
expect: 'readonly',
fetch: 'readonly',
navigator: 'readonly',
__DEV__: 'readonly',
XMLHttpRequest: 'readonly',
FormData: 'readonly',
React$Element: 'readonly',
requestAnimationFrame: 'readonly',
},
}

View File

@@ -29,19 +29,22 @@ test('download function', () => {
});
test('begin event', () => {
const mockedHeaders = { Etag: '123' }
return new Promise(resolve => {
const beginDT = RNBackgroundDownloader.download({
id: 'testBegin',
url: 'test',
destination: 'test'
}).begin((expectedBytes) => {
}).begin(({ expectedBytes, headers }) => {
expect(expectedBytes).toBe(9001);
expect(headers).toBe(mockedHeaders);
expect(beginDT.state).toBe('DOWNLOADING');
resolve();
});
NativeEventEmitter.listeners.downloadBegin({
id: 'testBegin',
expectedBytes: 9001
expectedBytes: 9001,
headers: mockedHeaders,
});
});
});

View File

@@ -1,3 +1,3 @@
module.exports = {
presets: ["module:metro-react-native-babel-preset"]
presets: ['module:metro-react-native-babel-preset'],
}

137
index.js
View File

@@ -1,112 +1,109 @@
import { NativeModules, NativeEventEmitter } from 'react-native';
const { RNBackgroundDownloader } = NativeModules;
const RNBackgroundDownloaderEmitter = new NativeEventEmitter(RNBackgroundDownloader);
import DownloadTask from './lib/downloadTask';
import { NativeModules, NativeEventEmitter } from 'react-native'
import DownloadTask from './lib/downloadTask'
const { RNBackgroundDownloader } = NativeModules
const RNBackgroundDownloaderEmitter = new NativeEventEmitter(RNBackgroundDownloader)
const tasksMap = new Map();
let headers = {};
const tasksMap = new Map()
let headers = {}
RNBackgroundDownloaderEmitter.addListener('downloadProgress', events => {
for (let event of events) {
let task = tasksMap.get(event.id);
if (task) {
task._onProgress(event.percent, event.written, event.total);
for (const event of events) {
const task = tasksMap.get(event.id)
if (task)
task._onProgress(event.percent, event.written, event.total)
}
}
});
})
RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, location }) => {
let task = tasksMap.get(id);
if (task) {
task._onDone({ location });
}
tasksMap.delete(id);
});
const task = tasksMap.get(id)
if (task)
task._onDone({ location })
tasksMap.delete(id)
})
RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
let task = tasksMap.get(event.id);
if (task) {
task._onError(event.error, event.errorcode);
}
tasksMap.delete(event.id);
});
const task = tasksMap.get(event.id)
if (task)
task._onError(event.error, event.errorcode)
tasksMap.delete(event.id)
})
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, expectedBytes, headers }) => {
let task = tasksMap.get(id);
if (task) {
task._onBegin({ expectedBytes, headers });
}
});
const task = tasksMap.get(id)
if (task)
task._onBegin({ expectedBytes, headers })
})
export function setHeaders(h = {}) {
if (typeof h !== 'object') {
throw new Error('[RNBackgroundDownloader] headers must be an object');
}
headers = h;
export function setHeaders (h = {}) {
if (typeof h !== 'object')
throw new Error('[RNBackgroundDownloader] headers must be an object')
headers = h
}
export function checkForExistingDownloads() {
export function checkForExistingDownloads () {
return RNBackgroundDownloader.checkForExistingDownloads()
.then(foundTasks => {
return foundTasks.map(taskInfo => {
let task = new DownloadTask(taskInfo,tasksMap.get(taskInfo.id));
const task = new DownloadTask(taskInfo, tasksMap.get(taskInfo.id))
if (taskInfo.state === RNBackgroundDownloader.TaskRunning) {
task.state = 'DOWNLOADING';
task.state = 'DOWNLOADING'
} else if (taskInfo.state === RNBackgroundDownloader.TaskSuspended) {
task.state = 'PAUSED';
task.state = 'PAUSED'
} else if (taskInfo.state === RNBackgroundDownloader.TaskCanceling) {
task.stop();
return null;
task.stop()
return null
} else if (taskInfo.state === RNBackgroundDownloader.TaskCompleted) {
if (taskInfo.bytesWritten === taskInfo.totalBytes) {
task.state = 'DONE';
} else {
if (taskInfo.bytesWritten === taskInfo.totalBytes)
task.state = 'DONE'
else
// IOS completed the download but it was not done.
return null;
return null
}
}
tasksMap.set(taskInfo.id, task);
return task;
}).filter(task => task !== null);
});
tasksMap.set(taskInfo.id, task)
return task
}).filter(task => task !== null)
})
}
export function completeHandler (jobId) {
return RNBackgroundDownloader.completeHandler(jobId)
}
export function download(options) {
if (!options.id || !options.url || !options.destination) {
throw new Error('[RNBackgroundDownloader] id, url and destination are required');
}
if (options.headers && typeof options.headers === 'object') {
export function download (options) {
if (!options.id || !options.url || !options.destination)
throw new Error('[RNBackgroundDownloader] id, url and destination are required')
if (options.headers && typeof options.headers === 'object')
options.headers = {
...headers,
...options.headers
};
} else {
options.headers = headers;
...options.headers,
}
RNBackgroundDownloader.download(options);
let task = new DownloadTask(options.id);
tasksMap.set(options.id, task);
return task;
else
options.headers = headers
RNBackgroundDownloader.download(options)
const task = new DownloadTask(options.id)
tasksMap.set(options.id, task)
return task
}
export const directories = {
documents: RNBackgroundDownloader.documents
};
documents: RNBackgroundDownloader.documents,
}
export const Network = {
WIFI_ONLY: RNBackgroundDownloader.OnlyWifi,
ALL: RNBackgroundDownloader.AllNetworks
};
ALL: RNBackgroundDownloader.AllNetworks,
}
export const Priority = {
HIGH: RNBackgroundDownloader.PriorityHigh,
MEDIUM: RNBackgroundDownloader.PriorityNormal,
LOW: RNBackgroundDownloader.PriorityLow
};
LOW: RNBackgroundDownloader.PriorityLow,
}
export default {
download,
@@ -115,5 +112,5 @@ export default {
setHeaders,
directories,
Network,
Priority
};
Priority,
}

View File

@@ -1,10 +1,9 @@
import { NativeModules } from 'react-native';
const { RNBackgroundDownloader } = NativeModules;
import { NativeModules } from 'react-native'
const { RNBackgroundDownloader } = NativeModules
function validateHandler(handler) {
if (!(typeof handler === 'function')) {
throw new TypeError(`[RNBackgroundDownloader] expected argument to be a function, got: ${typeof handler}`);
}
function validateHandler (handler) {
if (!(typeof handler === 'function'))
throw new TypeError(`[RNBackgroundDownloader] expected argument to be a function, got: ${typeof handler}`)
}
export default class DownloadTask {
state = 'PENDING'
@@ -14,12 +13,12 @@ export default class DownloadTask {
constructor (taskInfo, originalTask) {
if (typeof taskInfo === 'string') {
this.id = taskInfo;
this.id = taskInfo
} else {
this.id = taskInfo.id;
this.percent = taskInfo.percent;
this.bytesWritten = taskInfo.bytesWritten;
this.totalBytes = taskInfo.totalBytes;
this.id = taskInfo.id
this.percent = taskInfo.percent
this.bytesWritten = taskInfo.bytesWritten
this.totalBytes = taskInfo.totalBytes
}
if (originalTask) {
@@ -31,71 +30,67 @@ export default class DownloadTask {
}
begin (handler) {
validateHandler(handler);
this._beginHandler = handler;
return this;
validateHandler(handler)
this._beginHandler = handler
return this
}
progress (handler) {
validateHandler(handler);
this._progressHandler = handler;
return this;
validateHandler(handler)
this._progressHandler = handler
return this
}
done (handler) {
validateHandler(handler);
this._doneHandler = handler;
return this;
validateHandler(handler)
this._doneHandler = handler
return this
}
error (handler) {
validateHandler(handler);
this._errorHandler = handler;
return this;
validateHandler(handler)
this._errorHandler = handler
return this
}
_onBegin ({ expectedBytes, headers }) {
this.state = 'DOWNLOADING';
if (this._beginHandler) {
this._beginHandler({ expectedBytes, headers });
}
this.state = 'DOWNLOADING'
if (this._beginHandler)
this._beginHandler({ expectedBytes, headers })
}
_onProgress (percent, bytesWritten, totalBytes) {
this.percent = percent;
this.bytesWritten = bytesWritten;
this.totalBytes = totalBytes;
if (this._progressHandler) {
this._progressHandler(percent, bytesWritten, totalBytes);
}
this.percent = percent
this.bytesWritten = bytesWritten
this.totalBytes = totalBytes
if (this._progressHandler)
this._progressHandler(percent, bytesWritten, totalBytes)
}
_onDone ({ location }) {
this.state = 'DONE';
if (this._doneHandler) {
this._doneHandler({ location });
}
this.state = 'DONE'
if (this._doneHandler)
this._doneHandler({ location })
}
_onError (error, errorCode) {
this.state = 'FAILED';
if (this._errorHandler) {
this._errorHandler(error, errorCode);
}
this.state = 'FAILED'
if (this._errorHandler)
this._errorHandler(error, errorCode)
}
pause () {
this.state = 'PAUSED';
RNBackgroundDownloader.pauseTask(this.id);
this.state = 'PAUSED'
RNBackgroundDownloader.pauseTask(this.id)
}
resume () {
this.state = 'DOWNLOADING';
RNBackgroundDownloader.resumeTask(this.id);
this.state = 'DOWNLOADING'
RNBackgroundDownloader.resumeTask(this.id)
}
stop () {
this.state = 'STOPPED';
RNBackgroundDownloader.stopTask(this.id);
this.state = 'STOPPED'
RNBackgroundDownloader.stopTask(this.id)
}
}

View File

@@ -6,7 +6,7 @@
"scripts": {
"test": "jest",
"prepublish": "jest && npm run lint",
"lint": "eslint index.js lib/**"
"lint": "eslint ."
},
"repository": {
"type": "git",
@@ -40,15 +40,32 @@
}
],
"license": "Apache-2.0",
"lint-staged": {
"*.js": "eslint --cache"
},
"peerDependencies": {
"react-native": ">=0.57.0"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/runtime": "^7.16.3",
"@babel/eslint-parser": "^7.16.3",
"@react-native-community/eslint-config": "^3.0.1",
"eslint": "7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-config-standard-jsx": "^10.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-react": "^7.27.1",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-standard": "^5.0.0",
"@babel/core": "^7.4.0",
"@babel/runtime": "^7.4.2",
"@react-native-community/eslint-config": "^0.0.3",
"babel-jest": "^24.5.0",
"eslint": "^5.16.0",
"lint-staged": ">=12",
"immer": "^3.2.0",
"jest": "^24.5.0",
"metro-react-native-babel-preset": "^0.53.1",

View File

@@ -6,25 +6,25 @@
* @flow
*/
import React, { Component } from 'react';
import { Text, SafeAreaView, TextInput, Button, FlatList, View, AsyncStorage, TouchableOpacity, Slider } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import RNFS from 'react-native-fs';
import produce from 'immer';
import RNBGD from '../index';
import styles from './Style';
import React, { Component } from 'react'
import { Text, SafeAreaView, TextInput, Button, FlatList, View, AsyncStorage, TouchableOpacity, Slider } from 'react-native'
import Icon from 'react-native-vector-icons/Ionicons'
import RNFS from 'react-native-fs'
import produce from 'immer'
import RNBGD from '../index'
import styles from './Style'
const testURL = 'https://speed.hetzner.de/100MB.bin';
const urlRegex = /^(?:https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/;
const testURL = 'https://speed.hetzner.de/100MB.bin'
const urlRegex = /^(?:https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/
function isValid(url) {
return urlRegex.test(url);
function isValid (url) {
return urlRegex.test(url)
}
export default class App extends Component {
constructor(props) {
super(props);
this.idsToData = {};
constructor (props) {
super(props)
this.idsToData = {}
}
state = {
@@ -35,83 +35,82 @@ export default class App extends Component {
downloadsData: {},
};
async componentDidMount() {
const tasks = await RNBGD.checkForExistingDownloads();
async componentDidMount () {
const tasks = await RNBGD.checkForExistingDownloads()
if (tasks && tasks.length) {
await this.loadDownloads();
const downloadsData = {};
const downloads = [];
for (let task of tasks) {
downloads.push(task.id);
await this.loadDownloads()
const downloadsData = {}
const downloads = []
for (const task of tasks) {
downloads.push(task.id)
downloadsData[task.id] = {
url: this.idsToData[task.id].url,
percent: task.percent,
total: task.totalBytes,
status: task.state === 'DOWNLOADING' ? 'downloading' : 'paused',
task: task
};
this.attachToTask(task, this.idsToData[task.id].filePath);
task: task,
}
this.attachToTask(task, this.idsToData[task.id].filePath)
}
this.setState({
downloadsData,
downloads
});
downloads,
})
}
}
saveDownloads() {
AsyncStorage.setItem('idsToData', JSON.stringify(this.idsToData));
saveDownloads () {
AsyncStorage.setItem('idsToData', JSON.stringify(this.idsToData))
}
async loadDownloads() {
const mapStr = await AsyncStorage.getItem('idsToData');
async loadDownloads () {
const mapStr = await AsyncStorage.getItem('idsToData')
try {
this.idsToData = JSON.parse(mapStr) || {};
this.idsToData = JSON.parse(mapStr) || {}
} catch (e) {
console.error(e);
console.error(e)
}
}
pauseOrResume(id) {
let newStatus;
const download = this.state.downloadsData[id];
pauseOrResume (id) {
let newStatus
const download = this.state.downloadsData[id]
if (download.status === 'downloading') {
download.task.pause();
newStatus = 'paused';
download.task.pause()
newStatus = 'paused'
} else if (download.status === 'paused') {
download.task.resume();
newStatus = 'downloading';
download.task.resume()
newStatus = 'downloading'
} else {
console.error(`Unknown status for play or pause: ${download.status}`);
return;
console.error(`Unknown status for play or pause: ${download.status}`)
return
}
this.setState(produce(draft => {
draft.downloadsData[id].status = newStatus;
}));
draft.downloadsData[id].status = newStatus
}))
}
cancel(id) {
const download = this.state.downloadsData[id];
download.task.stop();
delete this.idsToData[id];
this.saveDownloads();
cancel (id) {
const download = this.state.downloadsData[id]
download.task.stop()
delete this.idsToData[id]
this.saveDownloads()
this.setState(produce(draft => {
delete draft.downloadsData[id];
draft.downloads.splice(draft.downloads.indexOf(id), 1);
}));
delete draft.downloadsData[id]
draft.downloads.splice(draft.downloads.indexOf(id), 1)
}))
}
renderRow({ item: downloadId }) {
const download = this.state.downloadsData[downloadId];
let iconName = 'ios-pause';
if (download.status === 'paused') {
iconName = 'ios-play';
}
renderRow ({ item: downloadId }) {
const download = this.state.downloadsData[downloadId]
let iconName = 'ios-pause'
if (download.status === 'paused')
iconName = 'ios-play'
return (
<View key={downloadId} style={styles.downloadItem}>
<View style={{flex: 1}}>
<View style={{ flex: 1 }}>
<View>
<Text>{downloadId}</Text>
<Text>{download.url}</Text>
@@ -130,77 +129,77 @@ export default class App extends Component {
</TouchableOpacity>
</View>
</View>
);
)
}
attachToTask(task, filePath) {
attachToTask (task, filePath) {
task.begin(expectedBytes => {
this.setState(produce(draft => {
draft.downloadsData[task.id].total = expectedBytes;
draft.downloadsData[task.id].status = 'downloading';
}));
draft.downloadsData[task.id].total = expectedBytes
draft.downloadsData[task.id].status = 'downloading'
}))
})
.progress(percent => {
this.setState(produce(draft => {
draft.downloadsData[task.id].percent = percent;
}));
draft.downloadsData[task.id].percent = percent
}))
})
.done(async() => {
.done(async () => {
try {
console.log(`Finished downloading: ${task.id}, deleting it...`);
await RNFS.unlink(filePath);
console.log(`Deleted ${task.id}`);
console.log(`Finished downloading: ${task.id}, deleting it...`)
await RNFS.unlink(filePath)
console.log(`Deleted ${task.id}`)
} catch (e) {
console.error(e);
console.error(e)
}
delete this.idsToData[task.id];
this.saveDownloads();
delete this.idsToData[task.id]
this.saveDownloads()
this.setState(produce(draft => {
delete draft.downloadsData[task.id];
draft.downloads.splice(draft.downloads.indexOf(task.id), 1);
}));
delete draft.downloadsData[task.id]
draft.downloads.splice(draft.downloads.indexOf(task.id), 1)
}))
})
.error(err => {
console.error(`Download ${task.id} has an error: ${err}`);
delete this.idsToData[task.id];
this.saveDownloads();
console.error(`Download ${task.id} has an error: ${err}`)
delete this.idsToData[task.id]
this.saveDownloads()
this.setState(produce(draft => {
delete draft.downloadsData[task.id];
draft.downloads.splice(draft.downloads.indexOf(task.id), 1);
}));
});
delete draft.downloadsData[task.id]
draft.downloads.splice(draft.downloads.indexOf(task.id), 1)
}))
})
}
addDownload() {
addDownload () {
const id = Math.random()
.toString(36)
.substr(2, 6);
const filePath = `${RNBGD.directories.documents}/${id}`;
const url = this.state.url || testURL;
.substr(2, 6)
const filePath = `${RNBGD.directories.documents}/${id}`
const url = this.state.url || testURL
const task = RNBGD.download({
id: id,
url: url,
destination: filePath,
});
this.attachToTask(task, filePath);
})
this.attachToTask(task, filePath)
this.idsToData[id] = {
url,
filePath
};
this.saveDownloads();
filePath,
}
this.saveDownloads()
this.setState(produce(draft => {
draft.downloadsData[id] = {
url: url,
status: 'idle',
task: task
};
draft.downloads.push(id);
draft.url = '';
}));
task: task,
}
draft.downloads.push(id)
draft.url = ''
}))
}
render() {
render () {
return (
<SafeAreaView style={styles.container}>
<TextInput
@@ -211,7 +210,7 @@ export default class App extends Component {
keyboardType="url"
placeholder={testURL}
onChangeText={(text) => {
this.setState({ url: text.toLowerCase() });
this.setState({ url: text.toLowerCase() })
}}
value={this.state.url}
/>
@@ -227,6 +226,6 @@ export default class App extends Component {
extraData={this.state.downloadsData}
/>
</SafeAreaView>
);
)
}
}

View File

@@ -1,4 +1,4 @@
import { StyleSheet } from 'react-native';
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
container: {
@@ -17,14 +17,14 @@ export default StyleSheet.create({
},
downloadingList: {
flex: 1,
width: '100%'
width: '100%',
},
downloadItem: {
height: 100,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 5,
justifyContent: 'space-between'
justifyContent: 'space-between',
},
buttonsContainer: {
flexDirection: 'row',
@@ -34,6 +34,6 @@ export default StyleSheet.create({
button: {
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 10
}
});
paddingHorizontal: 10,
},
})

View File

@@ -2,8 +2,8 @@
* @format
*/
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import { AppRegistry } from 'react-native'
import App from './App'
import { name as appName } from './app.json'
AppRegistry.registerComponent(appName, () => App);
AppRegistry.registerComponent(appName, () => App)

5697
yarn.lock

File diff suppressed because it is too large Load Diff