Fix image-upload, http-client and config

This commit is contained in:
Jerko Steiner 2019-09-15 17:35:31 +07:00
parent 50d36f268f
commit 69dd12375c
27 changed files with 282 additions and 266 deletions

View File

@ -13,14 +13,18 @@ rules:
ignorePattern: '^import .* from ' ignorePattern: '^import .* from '
comma-dangle: comma-dangle:
- warn - warn
- always-multiline - arrays: always-multiline
objects: always-multiline
imports: always-multiline
exports: always-multiline
functions: always-multiline
# semi: # semi:
# - warn # - warn
# - never # - never
# interface-name-prefix: # interface-name-prefix:
'@typescript-eslint/member-delimiter-style': '@typescript-eslint/member-delimiter-style':
- error - warn
- multiline: - multiline:
delimiter: none delimiter: none
singleline: singleline:

View File

@ -0,0 +1,4 @@
extends:
- ../../.eslintrc.yaml
rules:
'@typescript-eslint/no-explicit-any': off

View File

@ -4,7 +4,7 @@ export class Config {
get(key: string) { get(key: string) {
let value = this.config let value = this.config
key.split('.').forEach(k => { key.split('.').forEach(k => {
if (!value.hasOwnProperty(k)) { if (!Object.prototype.hasOwnProperty.call(value, k)) {
throw new Error(`Property "${k}" from "${key}" does not exist`) throw new Error(`Property "${k}" from "${key}" does not exist`)
} }
value = value[k] value = value[k]
@ -15,7 +15,7 @@ export class Config {
has(key: string) { has(key: string) {
let c = this.config let c = this.config
return key.split('.').every(k => { return key.split('.').every(k => {
const has = c.hasOwnProperty(k) const has = Object.prototype.hasOwnProperty.call(c, k)
if (has) { if (has) {
c = c[k] c = c[k]
} }

View File

@ -1,6 +1,6 @@
import {ConfigReader} from './ConfigReader'
import {join} from 'path'
import { writeFileSync } from 'fs' import { writeFileSync } from 'fs'
import { join } from 'path'
import { ConfigReader } from './ConfigReader'
describe('ConfigReader', () => { describe('ConfigReader', () => {

View File

@ -1,11 +1,11 @@
import loggerFactory, { Logger } from '@rondo.dev/logger'
import { readFileSync } from 'fs' import { readFileSync } from 'fs'
import YAML from 'js-yaml' import YAML from 'js-yaml'
import { join } from 'path' import { join } from 'path'
import { Config } from './Config' import { Config } from './Config'
import { findPackageRoot } from './findPackageRoot' import { findPackageRoot } from './findPackageRoot'
import loggerFactory, {ILogger} from '@rondo.dev/logger'
const isObject = (value: any) => value !== null && typeof value === 'object' const isObject = (value: unknown) => value !== null && typeof value === 'object'
export class ConfigReader { export class ConfigReader {
protected readonly config: any = {} protected readonly config: any = {}
@ -16,7 +16,7 @@ export class ConfigReader {
readonly path: string, readonly path: string,
readonly cwd: string | undefined = process.cwd(), readonly cwd: string | undefined = process.cwd(),
readonly environment = 'CONFIG', readonly environment = 'CONFIG',
readonly logger: ILogger = loggerFactory.getLogger('config'), readonly logger: Logger = loggerFactory.getLogger('config'),
) { ) {
const packageRoot = path && findPackageRoot(path) const packageRoot = path && findPackageRoot(path)
this.locations = packageRoot ? [packageRoot] : [] this.locations = packageRoot ? [packageRoot] : []
@ -93,7 +93,8 @@ export class ConfigReader {
// } // }
const value = src[key] const value = src[key]
if (isObject(value) && !Array.isArray(value)) { if (isObject(value) && !Array.isArray(value)) {
if (!dest.hasOwnProperty(key) || if (
!Object.prototype.hasOwnProperty.call(dest, key) ||
Array.isArray(dest[key]) || Array.isArray(dest[key]) ||
!isObject(dest[key]) !isObject(dest[key])
) { ) {

View File

@ -1,5 +1,5 @@
import {resolve, join} from 'path' import { Stats, statSync } from 'fs'
import {statSync, Stats} from 'fs' import { join, resolve } from 'path'
function findNearestDirectory( function findNearestDirectory(
dir: string, filename: string, dir: string, filename: string,

View File

@ -0,0 +1,4 @@
extends:
- ../../.eslintrc.yaml
rules:
'@typescript-eslint/no-explicit-any': off

View File

@ -1,140 +1,51 @@
import axios from 'axios' import {Method, Routes} from '@rondo.dev/http-types'
import {IHTTPClient} from './IHTTPClient' import {TypedRequestParams} from './TypedRequestParams'
import {IHeader} from './IHeader'
import {TMethod, IRoutes} from '@rondo.dev/http-types'
import {URLFormatter} from './URLFormatter'
import {IRequest} from './IRequest'
import {IResponse} from './IResponse'
import {ITypedRequestParams} from './ITypedRequestParams'
interface IRequestor { export interface HTTPClient<T extends Routes> {
request: (params: IRequest) => Promise<IResponse> request<
}
export class HTTPClient<T extends IRoutes> implements IHTTPClient<T> {
protected readonly requestor: IRequestor
protected readonly formatter: URLFormatter
constructor(
protected readonly baseURL = '',
protected readonly headers?: IHeader,
) {
this.requestor = this.createRequestor()
this.formatter = new URLFormatter()
}
protected createRequestor(): IRequestor {
return axios.create({
baseURL: this.baseURL,
headers: this.headers,
})
}
async request<
P extends keyof T & string, P extends keyof T & string,
M extends TMethod, M extends Method,
>(params: ITypedRequestParams<T, P, M>): Promise<T[P][M]['response']> { >(params: TypedRequestParams<T, P, M>): Promise<T[P][M]['response']>
const url = this.formatter.format(params.path, params.params)
const response = await this.requestor.request({
method: params.method,
url,
params: params.query,
data: params.body,
})
return response.data
}
get<P extends keyof T & string>( get<P extends keyof T & string>(
path: P, path: P,
query?: T[P]['get']['query'], query?: T[P]['get']['query'],
params?: T[P]['get']['params'], params?: T[P]['get']['params'],
) { ): Promise<T[P]['get']['response']>
return this.request({
method: 'get',
path,
query,
params,
})
}
post<P extends keyof T & string>( post<P extends keyof T & string>(
path: P, path: P,
body: T[P]['post']['body'], body: T[P]['post']['body'],
params?: T[P]['post']['params'], params?: T[P]['post']['params'],
) { ): Promise<T[P]['post']['response']>
return this.request({
method: 'post',
path,
body,
params,
})
}
put<P extends keyof T & string>( put<P extends keyof T & string>(
path: P, path: P,
body: T[P]['put']['body'], body: T[P]['put']['body'],
params?: T[P]['put']['params'], params?: T[P]['put']['params'],
) { ): Promise<T[P]['put']['response']>
return this.request({
method: 'put',
path,
body,
params,
})
}
delete<P extends keyof T & string>( delete<P extends keyof T & string>(
path: P, path: P,
body: T[P]['delete']['body'], body: T[P]['delete']['body'],
params?: T[P]['delete']['params'], params?: T[P]['delete']['params'],
) { ): Promise<T[P]['delete']['response']>
return this.request({
method: 'delete',
path,
body,
params,
})
}
head<P extends keyof T & string>( head<P extends keyof T & string>(
path: P, path: P,
query?: T[P]['head']['query'], query?: T[P]['head']['query'],
params?: T[P]['head']['params'], params?: T[P]['head']['params'],
) { ): Promise<T[P]['head']['response']>
return this.request({
method: 'head',
path,
params,
query,
})
}
options<P extends keyof T & string>( options<P extends keyof T & string>(
path: P, path: P,
query?: T[P]['options']['query'], query?: T[P]['options']['query'],
params?: T[P]['options']['params'], params?: T[P]['options']['params'],
) { ): Promise<T[P]['options']['response']>
return this.request({
method: 'options',
path,
params,
query,
})
}
patch<P extends keyof T & string>( patch<P extends keyof T & string>(
path: P, path: P,
body: T[P]['patch']['body'], body: T[P]['patch']['body'],
params?: T[P]['patch']['params'], params?: T[P]['patch']['params'],
) { ): Promise<T[P]['patch']['response']>
return this.request({
method: 'patch',
path,
body,
params,
})
}
} }

View File

@ -1,32 +1,32 @@
import {HTTPClient} from './HTTPClient' import {SimpleHTTPClient} from './SimpleHTTPClient'
import {IRequest} from './IRequest' import {Request} from './Request'
import {IResponse} from './IResponse' import {Response} from './Response'
import {IRoutes, TMethod} from '@rondo.dev/http-types' import {Routes, Method} from '@rondo.dev/http-types'
import {ITypedRequestParams} from './ITypedRequestParams' import {TypedRequestParams} from './TypedRequestParams'
interface IReqRes { interface ReqRes {
req: IRequest req: Request
res: IResponse res: Response
} }
export class HTTPClientError extends Error { export class HTTPClientError extends Error {
constructor(readonly request: IRequest, readonly response: IResponse) { constructor(readonly request: Request, readonly response: Response) {
super('HTTP Status: ' + response.status) super('HTTP Status: ' + response.status)
Error.captureStackTrace(this) Error.captureStackTrace(this)
} }
} }
export interface IRequestStatus { export interface RequestStatus {
request: IRequest request: Request
finished: boolean finished: boolean
} }
export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> { export class HTTPClientMock<T extends Routes> extends SimpleHTTPClient<T> {
mocks: {[key: string]: IResponse} = {} mocks: {[key: string]: Response} = {}
requests: IRequestStatus[] = [] requests: RequestStatus[] = []
protected waitPromise?: { protected waitPromise?: {
resolve: (r: IReqRes) => void resolve: (r: ReqRes) => void
reject: (err: Error) => void reject: (err: Error) => void
} }
@ -39,15 +39,15 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
*/ */
createRequestor() { createRequestor() {
return { return {
request: (req: IRequest): Promise<IResponse> => { request: (req: Request): Promise<Response> => {
const currentRequest: IRequestStatus = { const currentRequest: RequestStatus = {
request: req, request: req,
finished: false, finished: false,
} }
this.requests.push(currentRequest) this.requests.push(currentRequest)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const key = this.serialize(req) const key = this.serialize(req)
if (!this.mocks.hasOwnProperty(key)) { if (!Object.prototype.hasOwnProperty.call(this.mocks, key)) {
setImmediate(() => { setImmediate(() => {
const err = new Error( const err = new Error(
'No mock for request: ' + key + '\nAvailable mocks: ' + 'No mock for request: ' + key + '\nAvailable mocks: ' +
@ -76,7 +76,7 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
} }
} }
protected serialize(req: IRequest) { protected serialize(req: Request) {
return JSON.stringify({ return JSON.stringify({
method: req.method, method: req.method,
url: req.url, url: req.url,
@ -90,7 +90,7 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
* replaced. The signature is calculated using the `serialize()` method, * replaced. The signature is calculated using the `serialize()` method,
* which just does a `JSON.stringify(req)`. * which just does a `JSON.stringify(req)`.
*/ */
mockAdd(req: IRequest, data: any, status = 200): this { mockAdd(req: Request, data: any, status = 200): this {
this.mocks[this.serialize(req)] = {data, status} this.mocks[this.serialize(req)] = {data, status}
return this return this
} }
@ -98,8 +98,8 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
/** /**
* Adds a new mock with predefined type * Adds a new mock with predefined type
*/ */
mockAddTyped<P extends keyof T & string, M extends TMethod>( mockAddTyped<P extends keyof T & string, M extends Method>(
params: ITypedRequestParams<T, P, M>, params: TypedRequestParams<T, P, M>,
response: T[P][M]['response'], response: T[P][M]['response'],
): this { ): this {
const url = this.formatter.format(params.path, params.params) const url = this.formatter.format(params.path, params.params)
@ -120,7 +120,7 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
return this return this
} }
protected notify(r: IReqRes | Error) { protected notify(r: ReqRes | Error) {
if (!this.waitPromise) { if (!this.waitPromise) {
return return
} }
@ -147,12 +147,12 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
* const {req, res} = await httpMock.wait() * const {req, res} = await httpMock.wait()
* expect(req).toEqual({method:'get', url:'/auth/post', data: {...}}) * expect(req).toEqual({method:'get', url:'/auth/post', data: {...}})
*/ */
async wait(): Promise<IReqRes> { async wait(): Promise<ReqRes> {
if (this.requests.every(r => r.finished)) { if (this.requests.every(r => r.finished)) {
throw new Error('No requests to wait for') throw new Error('No requests to wait for')
} }
expect(this.waitPromise).toBe(undefined) expect(this.waitPromise).toBe(undefined)
const result: IReqRes = await new Promise((resolve, reject) => { const result: ReqRes = await new Promise((resolve, reject) => {
this.waitPromise = {resolve, reject} this.waitPromise = {resolve, reject}
}) })
// TODO think of a better way to do this. // TODO think of a better way to do this.

View File

@ -1,3 +1,3 @@
export interface IHeader { export interface Headers {
readonly [key: string]: string readonly [key: string]: string
} }

View File

@ -1,51 +0,0 @@
import {TMethod, IRoutes} from '@rondo.dev/http-types'
import {ITypedRequestParams} from './ITypedRequestParams'
export interface IHTTPClient<T extends IRoutes> {
request<
P extends keyof T & string,
M extends TMethod,
>(params: ITypedRequestParams<T, P, M>): Promise<T[P][M]['response']>
get<P extends keyof T & string>(
path: P,
query?: T[P]['get']['query'],
params?: T[P]['get']['params'],
): Promise<T[P]['get']['response']>
post<P extends keyof T & string>(
path: P,
body: T[P]['post']['body'],
params?: T[P]['post']['params'],
): Promise<T[P]['post']['response']>
put<P extends keyof T & string>(
path: P,
body: T[P]['put']['body'],
params?: T[P]['put']['params'],
): Promise<T[P]['put']['response']>
delete<P extends keyof T & string>(
path: P,
body: T[P]['delete']['body'],
params?: T[P]['delete']['params'],
): Promise<T[P]['delete']['response']>
head<P extends keyof T & string>(
path: P,
query?: T[P]['head']['query'],
params?: T[P]['head']['params'],
): Promise<T[P]['head']['response']>
options<P extends keyof T & string>(
path: P,
query?: T[P]['options']['query'],
params?: T[P]['options']['params'],
): Promise<T[P]['options']['response']>
patch<P extends keyof T & string>(
path: P,
body: T[P]['patch']['body'],
params?: T[P]['patch']['params'],
): Promise<T[P]['patch']['response']>
}

View File

@ -1,8 +0,0 @@
import {TMethod} from '@rondo.dev/http-types'
export interface IRequest {
method: TMethod,
url: string,
params?: {[key: string]: any},
data?: any,
}

View File

@ -1,13 +0,0 @@
import {IRoutes, TMethod} from '@rondo.dev/http-types'
export interface ITypedRequestParams<
T extends IRoutes,
P extends keyof T & string,
M extends TMethod,
> {
method: M,
path: P,
params?: T[P][M]['params'],
query?: T[P][M]['query'],
body?: T[P][M]['body'],
}

View File

@ -0,0 +1,8 @@
import {Method} from '@rondo.dev/http-types'
export interface Request {
method: Method
url: string
params?: {[key: string]: any}
data?: any
}

View File

@ -1,3 +1,3 @@
export interface IRequestQuery { export interface RequestParams {
[key: string]: string | number [key: string]: string | number
} }

View File

@ -1,3 +1,3 @@
export interface IRequestParams { export interface RequestQuery {
[key: string]: string | number [key: string]: string | number
} }

View File

@ -1,4 +1,4 @@
export interface IResponse { export interface Response {
data: any data: any
status: number status: number
} }

View File

@ -0,0 +1,140 @@
import axios from 'axios'
import {HTTPClient} from './HTTPClient'
import {Headers} from './Headers'
import {Method, Routes} from '@rondo.dev/http-types'
import {URLFormatter} from './URLFormatter'
import {Request} from './Request'
import {Response} from './Response'
import {TypedRequestParams} from './TypedRequestParams'
interface Requestor {
request: (params: Request) => Promise<Response>
}
export class SimpleHTTPClient<T extends Routes> implements HTTPClient<T> {
protected readonly requestor: Requestor
protected readonly formatter: URLFormatter
constructor(
protected readonly baseURL = '',
protected readonly headers?: Headers,
) {
this.requestor = this.createRequestor()
this.formatter = new URLFormatter()
}
protected createRequestor(): Requestor {
return axios.create({
baseURL: this.baseURL,
headers: this.headers,
})
}
async request<
P extends keyof T & string,
M extends Method,
>(params: TypedRequestParams<T, P, M>): Promise<T[P][M]['response']> {
const url = this.formatter.format(params.path, params.params)
const response = await this.requestor.request({
method: params.method,
url,
params: params.query,
data: params.body,
})
return response.data
}
get<P extends keyof T & string>(
path: P,
query?: T[P]['get']['query'],
params?: T[P]['get']['params'],
) {
return this.request({
method: 'get',
path,
query,
params,
})
}
post<P extends keyof T & string>(
path: P,
body: T[P]['post']['body'],
params?: T[P]['post']['params'],
) {
return this.request({
method: 'post',
path,
body,
params,
})
}
put<P extends keyof T & string>(
path: P,
body: T[P]['put']['body'],
params?: T[P]['put']['params'],
) {
return this.request({
method: 'put',
path,
body,
params,
})
}
delete<P extends keyof T & string>(
path: P,
body: T[P]['delete']['body'],
params?: T[P]['delete']['params'],
) {
return this.request({
method: 'delete',
path,
body,
params,
})
}
head<P extends keyof T & string>(
path: P,
query?: T[P]['head']['query'],
params?: T[P]['head']['params'],
) {
return this.request({
method: 'head',
path,
params,
query,
})
}
options<P extends keyof T & string>(
path: P,
query?: T[P]['options']['query'],
params?: T[P]['options']['params'],
) {
return this.request({
method: 'options',
path,
params,
query,
})
}
patch<P extends keyof T & string>(
path: P,
body: T[P]['patch']['body'],
params?: T[P]['patch']['params'],
) {
return this.request({
method: 'patch',
path,
body,
params,
})
}
}

View File

@ -0,0 +1,13 @@
import {Routes, Method} from '@rondo.dev/http-types'
export interface TypedRequestParams<
T extends Routes,
P extends keyof T & string,
M extends Method,
> {
method: M
path: P
params?: T[P][M]['params']
query?: T[P][M]['query']
body?: T[P][M]['body']
}

View File

@ -1,27 +1,27 @@
import {IRequestParams} from './IRequestParams' import {RequestParams} from './RequestParams'
import {IRequestQuery} from './IRequestQuery' import {RequestQuery} from './RequestQuery'
export interface IURLFormatterOptions { export interface URLFormatterOptions {
readonly baseURL: string readonly baseURL: string
readonly regex: RegExp readonly regex: RegExp
} }
export class URLFormatter { export class URLFormatter {
constructor(readonly params: IURLFormatterOptions = { constructor(readonly params: URLFormatterOptions = {
baseURL: '', baseURL: '',
regex: /:[a-zA-Z0-9-]+/g, regex: /:[a-zA-Z0-9-]+/g,
}) {} }) {}
format( format(
url: string, url: string,
params?: IRequestParams, params?: RequestParams,
query?: IRequestQuery, query?: RequestQuery,
) { ) {
let formattedUrl = url let formattedUrl = url
if (params) { if (params) {
formattedUrl = url.replace(this.params.regex, match => { formattedUrl = url.replace(this.params.regex, match => {
const key = match.substring(1) const key = match.substring(1)
if (!params.hasOwnProperty(key)) { if (!Object.prototype.hasOwnProperty.call(params, key)) {
throw new Error('Undefined URL paramter: ' + key) throw new Error('Undefined URL paramter: ' + key)
} }
return String(params![key]) return String(params![key])

View File

@ -1,10 +1,10 @@
export * from './HTTPClient' export * from './HTTPClient'
export * from './HTTPClientMock' export * from './HTTPClientMock'
export * from './IHeader' export * from './Headers'
export * from './IHTTPClient' export * from './HTTPClient'
export * from './IRequest' export * from './Request'
export * from './IRequestParams' export * from './RequestParams'
export * from './IRequestQuery' export * from './RequestQuery'
export * from './IResponse' export * from './Response'
export * from './ITypedRequestParams' export * from './TypedRequestParams'
export * from './URLFormatter' export * from './URLFormatter'

View File

@ -1,4 +1,6 @@
export type TMethod = 'get' /* eslint @typescript-eslint/no-explicit-any: 0 */
export type Method = 'get'
| 'post' | 'post'
| 'put' | 'put'
| 'delete' | 'delete'
@ -6,14 +8,14 @@ export type TMethod = 'get'
| 'head' | 'head'
| 'options' | 'options'
export interface IRoutes { export interface Routes {
// has to be any because otherwise TypeScript will start // has to be any because otherwise TypeScript will start
// throwing error and interfaces without an index signature // throwing error and interfaces without an index signature
// would not be usable // would not be usable
[route: string]: any [route: string]: any
} }
export interface IRoute { export interface Route {
params: any params: any
query: any query: any
body: any body: any

View File

@ -1,10 +1,9 @@
function attachErrorListener( function attachErrorListener(
reader: FileReader, reader: FileReader,
file: File, file: File,
reject: (err: Error) => void, reject: (err: Error) => void,
) { ) {
reader.onerror = ev => reject(new Error('Error reading file: ' + reader.onerror = () => reject(new Error('Error reading file: ' +
(reader.error ? reader.error.message : file.name))) (reader.error ? reader.error.message : file.name)))
} }

View File

@ -1,5 +1,6 @@
export async function createImageFromDataURL(dataURL: string) export async function createImageFromDataURL(
: Promise<HTMLImageElement> { dataURL: string,
): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const image = new Image() const image = new Image()
image.onload = () => resolve(image) image.onload = () => resolve(image)
@ -8,8 +9,9 @@ export async function createImageFromDataURL(dataURL: string)
}) })
} }
export async function drawCanvasFromDataURL(dataURL: string) export async function drawCanvasFromDataURL(
: Promise<HTMLCanvasElement> { dataURL: string,
): Promise<HTMLCanvasElement> {
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')! const ctx = canvas.getContext('2d')!
const image = await createImageFromDataURL(dataURL) const image = await createImageFromDataURL(dataURL)
@ -24,7 +26,7 @@ export async function getCanvasFromArrayBuffer(
width: number, width: number,
height: number, height: number,
) { ) {
return new Promise((resolve, reject) => { return new Promise(() => {
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')! const ctx = canvas.getContext('2d')!
const imageData = new ImageData( const imageData = new ImageData(

View File

@ -3,34 +3,33 @@ import {Resizer} from './Resizer'
import {readAsDataURL} from './Files' import {readAsDataURL} from './Files'
import {drawCanvasFromDataURL} from './Image' import {drawCanvasFromDataURL} from './Image'
export interface IImage { export interface Image {
dataURL: string dataURL: string
} }
export interface IImageUploadProps { export interface ImageUploadProps {
onChange: (images: IImage[]) => void onChange: (images: Image[]) => void
multiple: boolean multiple: boolean
} }
export class ImageUpload extends React.PureComponent<IImageUploadProps> { export class ImageUpload extends React.PureComponent<ImageUploadProps> {
fileInput: React.RefObject<HTMLInputElement> fileInput: React.RefObject<HTMLInputElement>
constructor(props: IImageUploadProps) { constructor(props: ImageUploadProps) {
super(props) super(props)
this.fileInput = React.createRef() this.fileInput = React.createRef()
} }
safeHandleChange = async (event: React.SyntheticEvent<HTMLInputElement>) => { safeHandleChange = async () => {
try { try {
await this.handleChange(event) await this.handleChange()
} catch (err) { } catch (err) {
// console.log('Error in handleChange', err) // console.log('Error in handleChange', err)
} }
} }
handleChange = async (event: React.SyntheticEvent<HTMLInputElement>) => { handleChange = async () => {
const self = this
const files = Array.from(this.fileInput.current!.files!) const files = Array.from(this.fileInput.current!.files!)
const resized: IImage[] = [] const resized: Image[] = []
for (const file of files) { for (const file of files) {
const dataURL = await readAsDataURL(file) const dataURL = await readAsDataURL(file)
const resizedDataURL = await this.resize(dataURL) const resizedDataURL = await this.resize(dataURL)
@ -44,7 +43,7 @@ export class ImageUpload extends React.PureComponent<IImageUploadProps> {
this.fileInput.current!.parentElement!.appendChild(img) this.fileInput.current!.parentElement!.appendChild(img)
} }
(window as any).resized = resized // (window as any).resized = resized
this.props.onChange(resized) this.props.onChange(resized)
} }

View File

@ -1,4 +1,4 @@
interface IWorkerParam { interface WorkerParam {
sourceWidth: number sourceWidth: number
sourceHeight: number sourceHeight: number
width: number width: number
@ -6,21 +6,22 @@ interface IWorkerParam {
source: ArrayBuffer source: ArrayBuffer
} }
interface IWorkerParamMessage { interface WorkerParamMessage {
data: IWorkerParam data: WorkerParam
} }
interface IWorkerResult { interface WorkerResult {
target: Uint8ClampedArray target: Uint8ClampedArray
} }
interface IWorkerResultMessage { interface WorkerResultMessage {
data: IWorkerResult data: WorkerResult
} }
function createResizeWorker(root: any = {}) function createResizeWorker(
: {onmessage: (event: IWorkerParamMessage) => void} { root: any = {}, // eslint-disable-line
root.onmessage = (event: IWorkerParamMessage) => { ): {onmessage: (event: WorkerParamMessage) => void} {
root.onmessage = (event: WorkerParamMessage) => {
const sourceWidth = event.data.sourceWidth const sourceWidth = event.data.sourceWidth
const sourceHeight = event.data.sourceHeight const sourceHeight = event.data.sourceHeight
const width = event.data.width const width = event.data.width
@ -32,7 +33,7 @@ function createResizeWorker(root: any = {})
const ratioHalfH = Math.ceil(ratioH / 2) const ratioHalfH = Math.ceil(ratioH / 2)
const source = new Uint8ClampedArray(event.data.source) const source = new Uint8ClampedArray(event.data.source)
const sourceH = source.length / sourceWidth / 4 // const sourceH = source.length / sourceWidth / 4
const targetSize = width * height * 4 const targetSize = width * height * 4
const targetMemory = new ArrayBuffer(targetSize) const targetMemory = new ArrayBuffer(targetSize)
const target = new Uint8ClampedArray(targetMemory, 0, targetSize) const target = new Uint8ClampedArray(targetMemory, 0, targetSize)
@ -92,10 +93,10 @@ function createResizeWorker(root: any = {})
} }
} }
const objData: IWorkerResult = { const objData: WorkerResult = {
target, target,
} }
postMessage(objData, [target.buffer] as any) postMessage(objData, [target.buffer] as any) // eslint-disable-line
} }
return root return root
} }
@ -156,7 +157,7 @@ export class Resizer {
const worker = new Worker(this.workerBlobURL) const worker = new Worker(this.workerBlobURL)
activeWorkers += 1 activeWorkers += 1
workers[c] = worker workers[c] = worker
worker.onmessage = (event: IWorkerResultMessage) => { worker.onmessage = (event: WorkerResultMessage) => {
worker.terminate() worker.terminate()
delete workers[c] delete workers[c]
activeWorkers -= 1 activeWorkers -= 1
@ -174,7 +175,7 @@ export class Resizer {
reject(new Error('Error resizing: ' + err.message)) reject(new Error('Error resizing: ' + err.message))
} }
const message: IWorkerParam = { const message: WorkerParam = {
sourceWidth, sourceWidth,
sourceHeight: partition.height, sourceHeight: partition.height,
width, width,