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 ADDED
@@ -0,0 +1,185 @@
1
+ # MobX downloader
2
+
3
+ File downloading SDK in Web browser, which is powered by [MobX][1].
4
+
5
+ [![MobX compatibility](https://img.shields.io/badge/Compatible-1?logo=mobx&label=MobX%206%2F7)][1]
6
+ [![NPM Dependency](https://img.shields.io/librariesio/github/idea2app/MobX-downloader.svg)][2]
7
+ [![CI & CD](https://github.com/idea2app/MobX-downloader/actions/workflows/main.yml/badge.svg)][3]
8
+
9
+ [![NPM](https://nodei.co/npm/mobx-downloader.png?downloads=true&downloadRank=true&stars=true)][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
@@ -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"}