mobx-downloader 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +185 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +861 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
- package/source/HTTP.ts +58 -0
- package/source/Task.ts +127 -0
- package/source/index.ts +73 -0
- package/tsconfig.json +22 -0
package/ReadMe.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# MobX downloader
|
|
2
|
+
|
|
3
|
+
File downloading SDK in Web browser, which is powered by [MobX][1].
|
|
4
|
+
|
|
5
|
+
[][1]
|
|
6
|
+
[][2]
|
|
7
|
+
[][3]
|
|
8
|
+
|
|
9
|
+
[][4]
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
```shell
|
|
16
|
+
npm i mobx-downloader
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
#### ESM issue
|
|
20
|
+
|
|
21
|
+
If you have issues with ESM, you can add the following configuration to your `package.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"resolutions": {
|
|
26
|
+
"native-file-system-adapter": "npm:@tech_query/native-file-system-adapter@^3.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
then install your project with [Yarn][5] or [PNPM][6].
|
|
32
|
+
|
|
33
|
+
### `tsconfig.json`
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"compilerOptions": {
|
|
38
|
+
"target": "ES6",
|
|
39
|
+
"moduleResolution": "Node",
|
|
40
|
+
"useDefineForClassFields": true,
|
|
41
|
+
"experimentalDecorators": false,
|
|
42
|
+
"jsx": "react-jsx"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `model/Downloader.ts`
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
import { Downloader } from 'mobx-downloader';
|
|
51
|
+
|
|
52
|
+
export const downloader = new Downloader();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `component/Downloader.tsx`
|
|
56
|
+
|
|
57
|
+
Source code: [https://github.com/idea2app/React-MobX-Bootstrap-ts/blob/master/src/component/Downloader.tsx][7]
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Icon } from 'idea-react';
|
|
61
|
+
import { observer } from 'mobx-react';
|
|
62
|
+
import { DownloadTask } from 'mobx-downloader';
|
|
63
|
+
import { FC } from 'react';
|
|
64
|
+
import { Button, Card, ProgressBar } from 'react-bootstrap';
|
|
65
|
+
|
|
66
|
+
import { downloader } from '../model/Downloader';
|
|
67
|
+
|
|
68
|
+
export const DTCard: FC<{ task: DownloadTask }> = observer(({ task }) => (
|
|
69
|
+
<Card>
|
|
70
|
+
<Card.Body>
|
|
71
|
+
<Card.Title>{task.name}</Card.Title>
|
|
72
|
+
<ProgressBar
|
|
73
|
+
striped={task.percent < 100}
|
|
74
|
+
animated={task.executing}
|
|
75
|
+
now={task.percent}
|
|
76
|
+
label={`${task.percent}%`}
|
|
77
|
+
/>
|
|
78
|
+
</Card.Body>
|
|
79
|
+
<Card.Footer className="d-flex justify-content-between align-items-center">
|
|
80
|
+
<small>
|
|
81
|
+
{task.loadedSize.toShortString()} /{' '}
|
|
82
|
+
{task.totalSize.toShortString()}
|
|
83
|
+
</small>
|
|
84
|
+
<div className="d-flex gap-3">
|
|
85
|
+
{task.percent < 100 &&
|
|
86
|
+
(task.executing ? (
|
|
87
|
+
<Button
|
|
88
|
+
size="sm"
|
|
89
|
+
variant="warning"
|
|
90
|
+
onClick={() => task.pause()}
|
|
91
|
+
>
|
|
92
|
+
<Icon name="pause" />
|
|
93
|
+
</Button>
|
|
94
|
+
) : (
|
|
95
|
+
<Button
|
|
96
|
+
size="sm"
|
|
97
|
+
variant="success"
|
|
98
|
+
onClick={() => task.start()}
|
|
99
|
+
>
|
|
100
|
+
<Icon name="play" />
|
|
101
|
+
</Button>
|
|
102
|
+
))}
|
|
103
|
+
<Button
|
|
104
|
+
size="sm"
|
|
105
|
+
variant="danger"
|
|
106
|
+
disabled={task.executing}
|
|
107
|
+
onClick={() => downloader.destroyTask(task.name)}
|
|
108
|
+
>
|
|
109
|
+
<Icon name="trash" />
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
</Card.Footer>
|
|
113
|
+
</Card>
|
|
114
|
+
));
|
|
115
|
+
|
|
116
|
+
export const Downloader: FC = observer(() => (
|
|
117
|
+
<ol className="list-unstyled d-flex flex-column gap-3">
|
|
118
|
+
{downloader.tasks.map(task => (
|
|
119
|
+
<li key={task.id}>
|
|
120
|
+
<DTCard task={task} />
|
|
121
|
+
</li>
|
|
122
|
+
))}
|
|
123
|
+
</ol>
|
|
124
|
+
));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `page/Downloader.tsx`
|
|
128
|
+
|
|
129
|
+
Source code: [https://github.com/idea2app/React-MobX-Bootstrap-ts/blob/master/src/page/Downloader.tsx][8]
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { observer } from 'mobx-react';
|
|
133
|
+
import { FC, FormEvent } from 'react';
|
|
134
|
+
import { Button, Container, FloatingLabel, Form } from 'react-bootstrap';
|
|
135
|
+
import { formToJSON } from 'web-utility';
|
|
136
|
+
|
|
137
|
+
import { Downloader } from '../component/Downloader';
|
|
138
|
+
import { downloader } from '../model/Downloader';
|
|
139
|
+
|
|
140
|
+
const addTask = async (event: FormEvent<HTMLFormElement>) => {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
|
|
143
|
+
const form = event.currentTarget;
|
|
144
|
+
const { path } = formToJSON(form);
|
|
145
|
+
|
|
146
|
+
await downloader
|
|
147
|
+
.createTask(path as string)
|
|
148
|
+
.start({ chunkSize: 1024 ** 2 / 2 });
|
|
149
|
+
|
|
150
|
+
form.reset();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const DownloaderPage: FC = observer(() => (
|
|
154
|
+
<Container>
|
|
155
|
+
<h1 className="my-4">Downloader</h1>
|
|
156
|
+
|
|
157
|
+
<Form
|
|
158
|
+
className="d-flex align-items-center gap-3 mb-3"
|
|
159
|
+
onSubmit={addTask}
|
|
160
|
+
>
|
|
161
|
+
<FloatingLabel className="flex-fill" label="URL">
|
|
162
|
+
<Form.Control
|
|
163
|
+
placeholder="URL"
|
|
164
|
+
type="url"
|
|
165
|
+
name="path"
|
|
166
|
+
required
|
|
167
|
+
/>
|
|
168
|
+
</FloatingLabel>
|
|
169
|
+
|
|
170
|
+
<Button type="submit">+</Button>
|
|
171
|
+
</Form>
|
|
172
|
+
|
|
173
|
+
<Downloader />
|
|
174
|
+
</Container>
|
|
175
|
+
));
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
[1]: https://mobx.js.org/
|
|
179
|
+
[2]: https://libraries.io/npm/mobx-downloader
|
|
180
|
+
[3]: https://github.com/idea2app/MobX-downloader/actions/workflows/main.yml
|
|
181
|
+
[4]: https://nodei.co/npm/mobx-downloader/
|
|
182
|
+
[5]: https://yarnpkg.com/
|
|
183
|
+
[6]: https://pnpm.io/
|
|
184
|
+
[7]: https://github.com/idea2app/React-MobX-Bootstrap-ts/blob/master/src/component/Downloader.tsx
|
|
185
|
+
[8]: https://github.com/idea2app/React-MobX-Bootstrap-ts/blob/master/src/page/Downloader.tsx
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Context, DownloadOptions, HTTPClient, TransferProgress } from "koajax";
|
|
2
|
+
import { FileSystemHandle } from "native-file-system-adapter";
|
|
3
|
+
import { ReadableStream } from "web-streams-polyfill";
|
|
4
|
+
import { ByteSize, Constructor } from "web-utility";
|
|
5
|
+
export abstract class DownloadTask implements Partial<TransferProgress> {
|
|
6
|
+
abstract client: Pick<HTTPClient<Context>, 'download'>;
|
|
7
|
+
accessor id: string;
|
|
8
|
+
accessor name: string;
|
|
9
|
+
accessor path: string;
|
|
10
|
+
accessor fsHandle: FileSystemHandle | undefined;
|
|
11
|
+
accessor total: number;
|
|
12
|
+
accessor loaded: number;
|
|
13
|
+
accessor percent: number;
|
|
14
|
+
accessor executing: boolean;
|
|
15
|
+
accessor options: DownloadOptions | undefined;
|
|
16
|
+
stream?: ReadableStream<Partial<TransferProgress>>;
|
|
17
|
+
get totalSize(): ByteSize;
|
|
18
|
+
get loadedSize(): ByteSize;
|
|
19
|
+
static nameOf(URI: string): string;
|
|
20
|
+
constructor(path: string, name?: string);
|
|
21
|
+
toJSON(): {
|
|
22
|
+
total: number;
|
|
23
|
+
loaded: number;
|
|
24
|
+
percent: number;
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
path: string;
|
|
28
|
+
fsHandle: FileSystemHandle;
|
|
29
|
+
options: DownloadOptions;
|
|
30
|
+
};
|
|
31
|
+
saveMeta(data?: Partial<TransferProgress>): Partial<TransferProgress>;
|
|
32
|
+
abstract loadStream(options?: DownloadOptions): AsyncGenerator<Partial<TransferProgress>>;
|
|
33
|
+
start(options?: DownloadOptions): Promise<ReadableStream<Partial<TransferProgress>>>;
|
|
34
|
+
pause(): Promise<void>;
|
|
35
|
+
destroy(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export class HTTPDownloadTask extends DownloadTask {
|
|
38
|
+
client: DownloadTask['client'];
|
|
39
|
+
constructor(path: string, name?: string);
|
|
40
|
+
loadStream(options?: DownloadOptions): AsyncGenerator<Partial<import("koajax").TransferProgress>, void, unknown>;
|
|
41
|
+
}
|
|
42
|
+
export class Downloader {
|
|
43
|
+
static protocolMap: Record<string, Constructor<DownloadTask>>;
|
|
44
|
+
static createTask(path: string, name?: string): DownloadTask;
|
|
45
|
+
constructor();
|
|
46
|
+
accessor tasks: DownloadTask[];
|
|
47
|
+
get unfinishedCount(): number;
|
|
48
|
+
get executingCount(): number;
|
|
49
|
+
createTask(path: string, name?: string): DownloadTask;
|
|
50
|
+
destroyTask(name: string): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":";;;;AAOA,OAAO,QAAQ,mBAAoB,YAAW,OAAO,CAAC,gBAAgB,CAAC;IACnE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;IAIvD,QAAQ,CAAC,EAAE,SAAM;IAIjB,QAAQ,CAAC,IAAI,SAAM;IAInB,QAAQ,CAAC,IAAI,SAAM;IAInB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAIhD,QAAQ,CAAC,KAAK,SAAK;IAInB,QAAQ,CAAC,MAAM,SAAK;IAIpB,QAAQ,CAAC,OAAO,SAAK;IAGrB,QAAQ,CAAC,SAAS,UAAS;IAI3B,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAAC;IAE9C,MAAM,CAAC,EAAE,eAAe,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAEnD,IACI,SAAS,aAEZ;IAED,IACI,UAAU,aAEb;IAED,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM;gBAMb,IAAI,EAAE,MAAM,EAAE,IAAI,SAA4B;IAK1D,MAAM;;;;;;;;;;IAUN,QAAQ,CAAC,IAAI,GAAE,OAAO,CAAC,gBAAgB,CAAM;IAQ7C,QAAQ,CAAC,UAAU,CACf,OAAO,CAAC,EAAE,eAAe,GAC1B,cAAc,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEtC,KAAK,CAAC,OAAO,kBAAe;IA2B5B,KAAK;IAIL,OAAO;CAKhB;ACxHD,6BAA8B,SAAQ,YAAY;IAC9C,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAE3B;gBAES,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAQhC,UAAU,CAAC,OAAO,CAAC,EAAE,eAAe;CAsC9C;AC/CD;IACI,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,YAAY,CAAC,CAAC,CAG3D;IAEF,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;;IAqB7C,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAM;IAEpC,IACI,eAAe,WAElB;IAED,IACI,cAAc,WAEjB;IAED,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAahC,WAAW,CAAC,IAAI,EAAE,MAAM;CAUjC","sources":["source/source/Task.ts","source/source/HTTP.ts","source/source/index.ts","source/index.ts"],"sourcesContent":[null,null,null,"import { computed, observable } from 'mobx';\nimport { persist, restore } from 'mobx-restful';\nimport { Constructor } from 'web-utility';\n\nimport { HTTPDownloadTask } from './HTTP';\nimport { DownloadTask } from './Task';\n\nexport * from './HTTP';\nexport * from './Task';\n\nexport class Downloader {\n static protocolMap: Record<string, Constructor<DownloadTask>> = {\n http: HTTPDownloadTask,\n https: HTTPDownloadTask\n };\n\n static createTask(path: string, name?: string) {\n const [protocol] = path.split(':');\n const ProtocolTask = Downloader.protocolMap[protocol];\n\n if (!ProtocolTask)\n throw new URIError(\n `Protocol \"${protocol} has not been registered\"`\n );\n return new ProtocolTask(path, name);\n }\n\n constructor() {\n restore(this, 'downloader');\n }\n\n @persist({\n set: tasks => tasks.map(({ name, path }) => ({ name, path })),\n get: list =>\n list?.map(({ name, path }) => Downloader.createTask(path, name))\n })\n @observable\n accessor tasks: DownloadTask[] = [];\n\n @computed\n get unfinishedCount() {\n return this.tasks.filter(({ percent }) => percent < 100).length;\n }\n\n @computed\n get executingCount() {\n return this.tasks.filter(({ executing }) => executing).length;\n }\n\n createTask(path: string, name?: string) {\n const { tasks } = this;\n const sameTask = tasks.find(task => task.path === path);\n\n if (sameTask) return sameTask;\n\n const task = Downloader.createTask(path, name);\n\n this.tasks = [...tasks, task];\n\n return task;\n }\n\n async destroyTask(name: string) {\n const { tasks } = this;\n const index = tasks.findIndex(({ name: N }) => N === name);\n\n if (index < 0) throw new ReferenceError(`${name} isn't found`);\n\n await tasks[index].destroy();\n\n this.tasks = [...tasks.slice(0, index), ...tasks.slice(index + 1)];\n }\n}\n"],"names":[],"version":3,"file":"index.d.ts.map"}
|