@netless/app-presentation 0.1.0-beta.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,36 @@
1
+ # @netless/app-presentation
2
+
3
+ A [Netless App](https://github.com/netless-io/netless-app) that display multiple images as presentation slides.
4
+
5
+ ## Install
6
+
7
+ <pre>npm add <strong>@netless/app-presentation</strong></pre>
8
+
9
+ ## Usage
10
+
11
+ ```js
12
+ import { register } from "@netless/fastboard"
13
+ import { install } from "@netless/app-presentation"
14
+
15
+ install(register, { as: 'DocsViewer' })
16
+ ```
17
+
18
+ ## Develop
19
+
20
+ See [Write you a Netless App](https://github.com/netless-io/fastboard/blob/main/docs/en/app.md).
21
+
22
+ To only develop the UI part, run:
23
+
24
+ ```bash
25
+ $ pnpm build
26
+ $ pnpm dev
27
+ ```
28
+
29
+ Then goto http://localhost:5173/ to see the app locally.
30
+
31
+ To develop it in a real whiteboard room, add a file .env.local containing the room's uuid and token,
32
+ then goto http://localhost:5173/e2e/.
33
+
34
+ ## License
35
+
36
+ MIT @ [netless](https://github.com/netless-io)
package/build.ts ADDED
@@ -0,0 +1,98 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import * as rollup from 'rollup'
4
+ import * as esbuild from 'esbuild'
5
+ import * as dts from '@hyrious/dts'
6
+ import * as SASS from 'sass'
7
+ import { version, peerDependencies } from './package.json'
8
+ import { createRequire } from 'node:module'
9
+
10
+ const sass = (): esbuild.Plugin => ({
11
+ name: 'inline-sass',
12
+ setup({ onLoad, esbuild }) {
13
+ onLoad({ filter: /\.scss/ }, async args => {
14
+ if (args.suffix !== '?inline') return
15
+ const { css } = SASS.compile(args.path, { style: 'compressed' })
16
+ const { outputFiles } = await esbuild.build({
17
+ stdin: {
18
+ contents: css,
19
+ loader: 'css',
20
+ resolveDir: path.dirname(args.path),
21
+ sourcefile: args.path,
22
+ },
23
+ logLevel: 'silent',
24
+ bundle: true,
25
+ minify: true,
26
+ write: false,
27
+ outdir: 'dist',
28
+ })
29
+ const contents = outputFiles[0].text.trimEnd()
30
+ return { contents, loader: 'text' }
31
+ })
32
+ }
33
+ })
34
+
35
+ // vanilla-lazyload has "browser": "dist/file.min.js", which is not ESM
36
+ // correct it by choosing the "module" field
37
+ const lazyload = (): esbuild.Plugin => ({
38
+ name: 'lazyload',
39
+ setup({ onResolve }) {
40
+ const require = createRequire(import.meta.url)
41
+ onResolve({ filter: /^vanilla-lazyload$/ }, args => {
42
+ const cjs = require.resolve(args.path)
43
+ const esm = cjs.replace('lazyload.min.js', 'lazyload.esm.js')
44
+ return { path: esm }
45
+ })
46
+ }
47
+ })
48
+
49
+ fs.rmSync('dist', { recursive: true, force: true })
50
+
51
+ let bundle = await rollup.rollup({
52
+ input: 'src/index.ts',
53
+ external: ['jspdf'],
54
+ plugins: [{
55
+ name: 'esbuild',
56
+ async load(id) {
57
+ const { outputFiles } = await esbuild.build({
58
+ entryPoints: [id],
59
+ bundle: true,
60
+ format: 'esm',
61
+ outfile: id.replace(/\.ts$/, '.js'),
62
+ sourcemap: true,
63
+ write: false,
64
+ target: ['es2017'],
65
+ plugins: [sass(), lazyload()],
66
+ define: {
67
+ __VERSION__: JSON.stringify(version)
68
+ },
69
+ external: Object.keys({
70
+ '@netless/window-manager': '*',
71
+ ...peerDependencies,
72
+ })
73
+ })
74
+ let code: any, map: any
75
+ for (const { path, text } of outputFiles) {
76
+ if (path.endsWith('.map')) map = text;
77
+ else code = text;
78
+ }
79
+ return { code, map }
80
+ }
81
+ }]
82
+ })
83
+
84
+ await Promise.all([
85
+ bundle.write({ file: 'dist/index.mjs', format: 'es', sourcemap: true, sourcemapExcludeSources: true }),
86
+ bundle.write({ file: 'dist/index.js', format: 'cjs', sourcemap: true, sourcemapExcludeSources: true, interop: 'auto', exports: 'named' }),
87
+ bundle.write({ file: 'dist/index.global.js', format: 'iife', name: 'NetlessAppPresentation', exports: 'named' }),
88
+ ])
89
+
90
+ await bundle.close()
91
+
92
+ // replace `import('jspdf')` in global js with `window.jspdf`
93
+ let code = fs.readFileSync('dist/index.global.js', 'utf-8')
94
+ code = code.replace(`await import('jspdf')`, `jspdf`)
95
+ fs.writeFileSync('dist/index.global.js', code)
96
+
97
+ if (process.env.DTS != '0')
98
+ await dts.build('src/index.ts', 'dist/index.d.ts', { exclude: ['@netless/window-manager'] })
@@ -0,0 +1,336 @@
1
+ import { View, AppContext, NetlessApp, WindowManager } from '@netless/window-manager';
2
+
3
+ /**
4
+ * A function that can be called to dispose resources.
5
+ */
6
+ type Disposer<T = any> = () => T;
7
+ /**
8
+ * An object that has a `dispose` method to dispose resources.
9
+ */
10
+ interface IDisposable<T = any> {
11
+ dispose(): T;
12
+ }
13
+ /**
14
+ * A union type of {@link Disposer} and {@link IDisposable} which can be called to dispose resources.
15
+ */
16
+ type DisposableType<T = any> = Disposer<T> | IDisposable<T>;
17
+ /**
18
+ * A combination of {@link Disposer} and {@link IDisposable}.
19
+ * It can be called to dispose resources or call the `dispose` method to dispose resources.
20
+ */
21
+ interface DisposableDisposer<T = any> {
22
+ (): T;
23
+ dispose(): T;
24
+ }
25
+
26
+ /**
27
+ * A Disposable Store is an {@link IDisposable} that manages {@link Disposer}s and {@link IDisposable}s.
28
+ *
29
+ * All {@link Disposer}s and {@link IDisposable}s in the store will be invoked(`flush`) when the store is disposed.
30
+ *
31
+ * A {@link DisposableStore} is also a {@link Disposer}, which means it can be the `dispose` method of an {@link IDisposable}.
32
+ *
33
+ * A {@link DisposableStore} is also an {@link IDisposable}, which means it can be managed by another {@link DisposableStore}.
34
+ *
35
+ */
36
+ interface DisposableStore extends DisposableDisposer {
37
+ /**
38
+ * Flush and clear all of the {@link Disposer}s and {@link IDisposable}s in the store.
39
+ */
40
+ (): void;
41
+ /**
42
+ * Get the number of {@link DisposableType}s in the store.
43
+ */
44
+ size(): number;
45
+ /**
46
+ * Add a {@link DisposableType} to the store.
47
+ *
48
+ * Do nothing if the {@link DisposableType} is already in the store.
49
+ *
50
+ * @param disposable A {@link DisposableType} .
51
+ * @returns The same {@link DisposableType} .
52
+ */
53
+ add<T extends DisposableType>(disposable: T): T;
54
+ /**
55
+ * Add multiple {@link DisposableType}s to the store.
56
+ *
57
+ * Do nothing if a {@link DisposableType} is already in the store.
58
+ *
59
+ * @param disposables An array of {@link DisposableType}s.
60
+ * @returns The same array of {@link DisposableType}s.
61
+ */
62
+ add<T extends DisposableType[]>(disposables: T): T;
63
+ /**
64
+ * Add each {@link DisposableType} to the store.
65
+ *
66
+ * Do nothing if a {@link DisposableType} is already in the store.
67
+ *
68
+ * @param disposables An array of {@link DisposableType}s.
69
+ * @returns The same array of {@link DisposableType}s.
70
+ */
71
+ add<T extends DisposableType>(disposables: T | T[]): T | T[];
72
+ /**
73
+ * Invoke the executor function and add the returned {@link DisposableType} to the store.
74
+ *
75
+ * Do nothing if the {@link DisposableType} is already in the store.
76
+ *
77
+ * @param executor A function that returns a {@link DisposableType}.
78
+ * @returns The returned {@link DisposableType}.
79
+ */
80
+ make<T extends DisposableType>(executor: () => T): T;
81
+ /**
82
+ * Invoke the executor function and add the returned {@link DisposableType} to the store.
83
+ *
84
+ * Do nothing if `null | undefined` is returned or the returned {@link DisposableType} is already in the store.
85
+ *
86
+ * @param executor A function that returns either a {@link DisposableType} or `null`.
87
+ * @returns The returned {@link DisposableType}, or `undefined` if the executor returns `null`.
88
+ */
89
+ make<T extends DisposableType>(executor: () => T | null | undefined | void): T | void;
90
+ /**
91
+ * Invoke the executor function and add each {@link DisposableType} in the returned array to the store.
92
+ *
93
+ * Do nothing if a {@link DisposableType} is already in the store.
94
+ *
95
+ * @param executor A function that returns an array of {@link DisposableType}s.
96
+ * @returns The returned array of {@link DisposableType}s.
97
+ */
98
+ make<T extends DisposableType[]>(executor: () => T): T;
99
+ /**
100
+ * Invoke the executor function and the returned {@link DisposableType}s to the store. Do nothing if `undefined | null` is returned.
101
+ *
102
+ * Do nothing if a {@link DisposableType} is already in the store.
103
+ *
104
+ * @param executor A function that returns either an array of {@link DisposableType}s or `undefined | null`.
105
+ * @returns The returned array of {@link DisposableType}s, or `undefined` if the executor returns `undefined | null`.
106
+ */
107
+ make<T extends DisposableType[]>(executor: () => T | null | void | undefined): T | void;
108
+ /**
109
+ * Invoke the executor function and the returned {@link DisposableType}s to the store. Do nothing if `undefined | null` is returned.
110
+ *
111
+ * Do nothing if a {@link DisposableType} is already in the store.
112
+ *
113
+ * @param executor A function that returns either an array of {@link DisposableType}s or `undefined | null`.
114
+ * @returns The returned array of {@link DisposableType}s, or `undefined` if the executor returns `undefined | null`.
115
+ */
116
+ make<T extends DisposableType>(executor: () => T | T[] | null | void | undefined): T | T[] | void;
117
+ /**
118
+ * Check if a {@link DisposableType} is in the store.
119
+ *
120
+ * @param disposable The {@link DisposableType}.
121
+ * @returns `true` if the {@link DisposableType} is in the store, otherwise `false`.
122
+ */
123
+ has(disposable: DisposableType): boolean;
124
+ /**
125
+ * Remove the {@link DisposableType} from the store. Does not invoke the removed {@link DisposableType}.
126
+ *
127
+ * @param disposable The {@link DisposableType} to be flushed.
128
+ * @returns `true` if the {@link DisposableType} is found and removed, otherwise `false`.
129
+ */
130
+ remove(disposable: DisposableType): boolean;
131
+ /**
132
+ * Invoke the {@link DisposableType} and remove it from the store at the specific key.
133
+ *
134
+ * @param disposable The {@link DisposableType} to be flushed. Flush all if omitted.
135
+ */
136
+ flush(disposable?: DisposableType): void;
137
+ /**
138
+ * Flush and clear all of the {@link Disposer}s and {@link IDisposable}s in the store.
139
+ */
140
+ dispose(this: void): void;
141
+ }
142
+
143
+ interface ILazyLoadInstance {
144
+ /**
145
+ * Make LazyLoad to re-check the DOM for `elements_selector` elements inside its `container`.
146
+ *
147
+ * ### Use case
148
+ *
149
+ * Update LazyLoad after you added or removed DOM elements to the page.
150
+ */
151
+ update: (elements?: NodeListOf<HTMLElement>) => void;
152
+
153
+ /**
154
+ * Destroys the instance, unsetting instance variables and removing listeners.
155
+ *
156
+ * ### Use case
157
+ *
158
+ * Free up some memory. Especially useful for Single Page Applications.
159
+ */
160
+ destroy: () => void;
161
+
162
+ /**
163
+ * Loads all the lazy elements right away and stop observing them,
164
+ * no matter if they are inside or outside the viewport,
165
+ * no matter if they are hidden or visible.
166
+ *
167
+ * ### Use case
168
+ *
169
+ * To load all the remaining elements in advance
170
+ */
171
+ loadAll: () => void;
172
+
173
+ /**
174
+ * Restores DOM to its original state. Note that it doesn't destroy LazyLoad,
175
+ * so you probably want to use it along with destroy().
176
+ *
177
+ * ### Use case
178
+ *
179
+ * Reset the DOM before a soft page navigation (SPA) occures, e.g. using TurboLinks.
180
+ */
181
+ restoreAll: () => void;
182
+
183
+ /**
184
+ * The number of elements that are currently downloading from the network
185
+ * (limitedly to the ones managed by the instance of LazyLoad).
186
+ * This is particularly useful to understand whether
187
+ * or not is safe to destroy this instance of LazyLoad.
188
+ */
189
+ loadingCount: number;
190
+
191
+ /**
192
+ * The number of elements that haven't been lazyloaded yet
193
+ * (limitedly to the ones managed by the instance of LazyLoad)
194
+ */
195
+ toLoadCount: number;
196
+ }
197
+
198
+ declare class Preload implements IDisposable {
199
+ readonly pages: PresentationPage[];
200
+ readonly links: HTMLLinkElement[];
201
+ head: number;
202
+ tail: number;
203
+ timer: number;
204
+ get done(): boolean;
205
+ constructor(pages: PresentationPage[]);
206
+ touch(index: number): void;
207
+ handler(): void;
208
+ dispose(): void;
209
+ }
210
+
211
+ interface PresentationPage {
212
+ src: string;
213
+ width: number;
214
+ height: number;
215
+ thumbnail?: string | undefined;
216
+ }
217
+ interface PresentationConfig {
218
+ readonly pages: PresentationPage[];
219
+ readonly readonly?: boolean;
220
+ }
221
+ /**
222
+ * Standalone presentation slide viewer.
223
+ *
224
+ * ```html
225
+ * <div class="netless-app-presentation">
226
+ * <div class="netless-app-presentation-content netless-app-presentation-readonly">
227
+ * <div class="netless-app-presentation-preview-mask"></div>
228
+ * <div class="netless-app-presentation-preview">
229
+ * <a class="netless-app-presentation-preview-page">
230
+ * <img :data-src="thumbnail || src">
231
+ * <span class="netless-app-presentation-preview-page-name">1</span>
232
+ * </a>
233
+ * </div>
234
+ * <div class="netless-app-presentation-image">
235
+ * <img :src="src">
236
+ * </div>
237
+ * <div class="netless-app-presentation-wb-view" style="pointer-events: auto"></div>
238
+ * </div>
239
+ * <div class="netless-app-presentation-footer netless-app-presentation-readonly">
240
+ * <button class="netless-app-presentation-footer-btn netless-app-presentation-btn-sidebar">
241
+ * <svg class="netless-app-presentation-footer-icon-sidebar"></svg>
242
+ * </button>
243
+ * <div class="netless-app-presentation-page-jumps">
244
+ * <button-page-back />
245
+ * <button-page-next />
246
+ * </div>
247
+ * <div class="netless-app-presentation-page-number">
248
+ * <input class="netless-app-presentation-page-number-input">
249
+ * <span> / 10</span>
250
+ * </div>
251
+ * </div>
252
+ * </div>
253
+ * ```
254
+ */
255
+ declare class Presentation implements IDisposable<void> {
256
+ readonly namespace = "netless-app-presentation";
257
+ readonly dispose: DisposableStore;
258
+ readonly pages: PresentationPage[];
259
+ readonly preload: Preload;
260
+ dom: Element | DocumentFragment;
261
+ contentDOM: HTMLDivElement;
262
+ previewDOM: HTMLDivElement;
263
+ imageDOM: HTMLDivElement;
264
+ image: HTMLImageElement;
265
+ whiteboardDOM: HTMLDivElement;
266
+ footerDOM: HTMLDivElement;
267
+ pageNumberInputDOM: HTMLInputElement;
268
+ readonly: boolean;
269
+ initialized: boolean;
270
+ showPreview: boolean;
271
+ pageIndex: number;
272
+ previewLazyload: ILazyLoadInstance | null;
273
+ constructor(config: PresentationConfig);
274
+ initialize(): void;
275
+ setDOM(dom: Element | DocumentFragment): void;
276
+ setReadonly(readonly: boolean): void;
277
+ setPageIndex(pageIndex: number): void;
278
+ togglePreview(showPreview?: boolean): void;
279
+ onNewPageIndex(index: number, _origin: "navigation" | "keydown" | "input" | "preview"): void;
280
+ page(): PresentationPage | undefined;
281
+ updateImage(): void;
282
+ private c;
283
+ private isEditable;
284
+ private x_oss_process;
285
+ }
286
+
287
+ declare const __inline: string;
288
+
289
+ type Logger = (...data: any[]) => void;
290
+ interface PresentationAppOptions {
291
+ log?: Logger;
292
+ }
293
+ interface PresentationController {
294
+ readonly app: Presentation;
295
+ readonly view: View;
296
+ readonly context: AppContext;
297
+ /** Returns false if failed to jump (either because out of bounds or lack of permissions). */
298
+ jumpPage(index: number): boolean;
299
+ /** Returns false if failed to jump */
300
+ prevPage(): boolean;
301
+ /** Returns false if failed to jump */
302
+ nextPage(): boolean;
303
+ /** `index` ranges from 0 to `length - 1` */
304
+ pageState(): {
305
+ index: number;
306
+ length: number;
307
+ };
308
+ toPdf(): Promise<{
309
+ pdf: ArrayBuffer;
310
+ title: string;
311
+ } | null>;
312
+ log: Logger;
313
+ }
314
+
315
+ declare const NetlessAppPresentation: NetlessApp<{}, unknown, PresentationAppOptions, PresentationController>;
316
+ type RegisterFn = typeof WindowManager["register"];
317
+ interface InstallOptions {
318
+ /**
319
+ * Register as another "kind", to hijack existing apps.
320
+ * The default kind is "Presentation".
321
+ *
322
+ * @example "DocsViewer"
323
+ */
324
+ as?: string;
325
+ }
326
+ /**
327
+ * Call `register({ kind: "Presentation", src: NetlessAppPresentation })` to register this app.
328
+ * Optionally accepts an options object to override the default kind.
329
+ *
330
+ * @example install(register, { as: "DocsViewer" })
331
+ */
332
+ declare const install: (register: RegisterFn, options?: InstallOptions) => Promise<void>;
333
+
334
+ declare const version: string;
335
+
336
+ export { type InstallOptions, type Logger, NetlessAppPresentation, Presentation, type PresentationAppOptions, type PresentationConfig, type PresentationController, type PresentationPage, type RegisterFn, NetlessAppPresentation as default, install, __inline as styles, version };