@netless/app-slide 0.2.99 → 0.2.100-alpha.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/dist/index.d.ts +59 -5
- package/dist/main.cjs.js +340 -311
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +3588 -2377
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +335 -306
- package/dist/main.iife.js.map +1 -1
- package/package.json +11 -9
- package/src/DocsViewer/ResizableContainer.ts +5 -12
- package/src/SlideController/index.ts +16 -12
- package/src/SlideController/recovery.ts +17 -0
- package/src/SlideDocsViewer/index.ts +138 -3
- package/src/index.ts +8 -3
- package/LICENSE +0 -21
package/package.json
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/app-slide",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.100-alpha.0",
|
|
4
4
|
"main": "dist/main.cjs.js",
|
|
5
5
|
"module": "dist/main.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node __tests__/recovery.test.js",
|
|
9
|
+
"types": "node scripts/build-types.mjs",
|
|
10
|
+
"build": "vite build && npm run types",
|
|
11
|
+
"build:dev": "vite build --mode development && npm run types",
|
|
12
|
+
"cleanup": "rimraf ./dist"
|
|
13
|
+
},
|
|
7
14
|
"files": [
|
|
8
15
|
"src",
|
|
9
16
|
"dist",
|
|
10
17
|
"README-zh.md"
|
|
11
18
|
],
|
|
12
19
|
"devDependencies": {
|
|
13
|
-
"@netless/slide": "1.4.
|
|
20
|
+
"@netless/slide": "1.4.55-alpha.3",
|
|
14
21
|
"@types/color-string": "^1.5.2",
|
|
15
22
|
"color-string": "^1.9.1",
|
|
16
23
|
"jspdf": "2.5.1",
|
|
24
|
+
"@juggle/resize-observer": "^3.3.1",
|
|
17
25
|
"side-effect-manager": "^1.2.1",
|
|
18
26
|
"vanilla-lazyload": "^17.8.3"
|
|
19
27
|
},
|
|
@@ -24,11 +32,5 @@
|
|
|
24
32
|
"jspdf": {
|
|
25
33
|
"optional": true
|
|
26
34
|
}
|
|
27
|
-
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"types": "node scripts/build-types.mjs",
|
|
30
|
-
"build": "vite build && npm run types",
|
|
31
|
-
"build:dev": "vite build --mode development && npm run types",
|
|
32
|
-
"cleanup": "rimraf ./dist"
|
|
33
35
|
}
|
|
34
|
-
}
|
|
36
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Slide } from "@netless/slide";
|
|
2
2
|
import { ScrollBar } from "./ScrollBar";
|
|
3
|
-
import type { AppContext } from "@netless/window-manager";
|
|
3
|
+
import type { AppContext, ReadonlyTeleBox } from "@netless/window-manager";
|
|
4
4
|
import type { Attributes, MagixEvents } from "../typings";
|
|
5
5
|
import type { AppOptions } from "../index";
|
|
6
6
|
|
|
@@ -14,7 +14,6 @@ export class ResizableContainer {
|
|
|
14
14
|
private whiteboardContainer: HTMLDivElement | null = null;
|
|
15
15
|
private slide: Slide | null = null;
|
|
16
16
|
private scale = 1;
|
|
17
|
-
private resizeObserver: ResizeObserver | null = null;
|
|
18
17
|
private slideWidth = 1;
|
|
19
18
|
private slideHeight = 1;
|
|
20
19
|
private scrollBar: ScrollBar | null = null;
|
|
@@ -29,7 +28,8 @@ export class ResizableContainer {
|
|
|
29
28
|
constructor(
|
|
30
29
|
parent: HTMLElement,
|
|
31
30
|
context: AppContext<Attributes, MagixEvents, AppOptions>,
|
|
32
|
-
enableResize: boolean
|
|
31
|
+
enableResize: boolean,
|
|
32
|
+
_box?: ReadonlyTeleBox,
|
|
33
33
|
) {
|
|
34
34
|
this.enableResize = enableResize;
|
|
35
35
|
this.parent = parent;
|
|
@@ -55,13 +55,7 @@ export class ResizableContainer {
|
|
|
55
55
|
// 初始化滚动条
|
|
56
56
|
this.scrollBar = new ScrollBar(this.root, this);
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
60
|
-
this.updateResizableContainer();
|
|
61
|
-
});
|
|
62
|
-
this.resizeObserver.observe(this.scrollContainer);
|
|
63
|
-
|
|
64
|
-
}
|
|
58
|
+
};
|
|
65
59
|
|
|
66
60
|
public getTranslate(): { x: number; y: number } {
|
|
67
61
|
return { x: this.translateX, y: this.translateY };
|
|
@@ -204,8 +198,7 @@ export class ResizableContainer {
|
|
|
204
198
|
return this.scale;
|
|
205
199
|
}
|
|
206
200
|
|
|
207
|
-
public destroy(): void {
|
|
208
|
-
this.resizeObserver?.disconnect();
|
|
201
|
+
public destroy(_box?: ReadonlyTeleBox): void {
|
|
209
202
|
this.scrollBar?.destroy();
|
|
210
203
|
}
|
|
211
204
|
}
|
|
@@ -19,6 +19,7 @@ import { clamp } from "../utils/helpers";
|
|
|
19
19
|
import { cachedGetBgColor } from "../utils/bgcolor";
|
|
20
20
|
import { log, verbose, setRoomLogger } from "../utils/logger";
|
|
21
21
|
import { getRoomTracker } from "../utils/tracker";
|
|
22
|
+
import { releaseAndRestoreSlide } from "./recovery";
|
|
22
23
|
export { syncSceneWithSlide, createDocsViewerPages } from "./helpers";
|
|
23
24
|
|
|
24
25
|
export const DefaultUrl = "https://convertcdn.netless.link/dynamicConvert";
|
|
@@ -315,6 +316,7 @@ export class SlideControllerBase {
|
|
|
315
316
|
anchor,
|
|
316
317
|
interactive: true,
|
|
317
318
|
mode: "interactive",
|
|
319
|
+
syncEventQueuePolicy: options.syncEventQueuePolicy ?? "fifo",
|
|
318
320
|
controller: false,
|
|
319
321
|
enableGlobalClick: options.enableGlobalClick ?? true,
|
|
320
322
|
renderOptions: {
|
|
@@ -342,6 +344,8 @@ export class SlideControllerBase {
|
|
|
342
344
|
skipActionWhenFrozen: options.skipActionWhenFrozen ?? true,
|
|
343
345
|
resourceMaxRetries: options.resourceMaxRetries,
|
|
344
346
|
onResourceMaxRetries: options.onResourceMaxRetries,
|
|
347
|
+
// Slide 内部 ResizeObserver 关掉,改由 app-slide 的 observer + boxSizeChange 一起驱动
|
|
348
|
+
disableFrameResizeObserver: true,
|
|
345
349
|
});
|
|
346
350
|
if (import.meta.env.DEV) {
|
|
347
351
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -392,20 +396,20 @@ export class SlideControllerBase {
|
|
|
392
396
|
if (!this.visible) return;
|
|
393
397
|
this.isFrozen = false;
|
|
394
398
|
if (this.ready) {
|
|
395
|
-
let isNeedSyncState = false;
|
|
396
399
|
log("[Slide] unfreeze", this.context.appId);
|
|
397
|
-
|
|
398
|
-
this.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.slide.resume();
|
|
402
|
-
}
|
|
403
|
-
if (isNeedSyncState) {
|
|
404
|
-
const state = this.context.storage.state.state;
|
|
405
|
-
if (state) {
|
|
406
|
-
log("[Slide] sync storage", JSON.stringify(state));
|
|
407
|
-
this.slide.setSlideState(state);
|
|
400
|
+
try {
|
|
401
|
+
if (this.invisibleBehavior !== "frozen") {
|
|
402
|
+
this.slide.resume();
|
|
403
|
+
return;
|
|
408
404
|
}
|
|
405
|
+
|
|
406
|
+
await releaseAndRestoreSlide(
|
|
407
|
+
this.slide,
|
|
408
|
+
() => this.context.storage.state.state,
|
|
409
|
+
state => log("[Slide] sync storage", JSON.stringify(state))
|
|
410
|
+
);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
log("[Slide] unfreeze failed", error);
|
|
409
413
|
}
|
|
410
414
|
} else {
|
|
411
415
|
this._toFreeze = -1;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface RecoverableSlide<State> {
|
|
2
|
+
release: (callback: () => void) => void;
|
|
3
|
+
setSlideState: (state: State) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export async function releaseAndRestoreSlide<State>(
|
|
7
|
+
slide: RecoverableSlide<State>,
|
|
8
|
+
getLatestState: () => State | null | undefined,
|
|
9
|
+
onRestore?: (state: State) => void
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
await new Promise<void>(resolve => slide.release(resolve));
|
|
12
|
+
const state = getLatestState();
|
|
13
|
+
if (state) {
|
|
14
|
+
onRestore?.(state);
|
|
15
|
+
await slide.setSlideState(state);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -16,6 +16,24 @@ import { logger } from "../utils/logger";
|
|
|
16
16
|
import { isEditable } from "../utils/helpers";
|
|
17
17
|
import type { Attributes, MagixEvents } from "../typings";
|
|
18
18
|
import type { AppOptions } from "..";
|
|
19
|
+
import type { SyncEventQueuePolicy } from "@netless/slide";
|
|
20
|
+
import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
|
|
21
|
+
|
|
22
|
+
type BoxSize = { width: number; height: number };
|
|
23
|
+
|
|
24
|
+
const ResizeObserverImpl = window.ResizeObserver || ResizeObserverPolyfill;
|
|
25
|
+
const BOX_SIZE_EPSILON = 0.5;
|
|
26
|
+
|
|
27
|
+
const formatBoxSize = ({ width, height }: BoxSize): string =>
|
|
28
|
+
`${width.toFixed(1)} x ${height.toFixed(1)} px`;
|
|
29
|
+
|
|
30
|
+
const sizesEqual = (left?: BoxSize, right?: BoxSize): boolean =>
|
|
31
|
+
Boolean(
|
|
32
|
+
left &&
|
|
33
|
+
right &&
|
|
34
|
+
Math.abs(left.width - right.width) <= BOX_SIZE_EPSILON &&
|
|
35
|
+
Math.abs(left.height - right.height) <= BOX_SIZE_EPSILON
|
|
36
|
+
);
|
|
19
37
|
|
|
20
38
|
export const ClickThroughAppliances = new Set(["clicker"]);
|
|
21
39
|
|
|
@@ -60,7 +78,15 @@ export class SlideDocsViewer {
|
|
|
60
78
|
protected readonly appId: string;
|
|
61
79
|
protected isViewMounted = false;
|
|
62
80
|
protected justSildeReadonly = false;
|
|
81
|
+
protected syncEventQueuePolicy: SyncEventQueuePolicy = "fifo";
|
|
63
82
|
private enableScale: boolean;
|
|
83
|
+
private boxSizeEventCount = 0;
|
|
84
|
+
private latestBoxSize: BoxSize | undefined;
|
|
85
|
+
private lastObserverSize: BoxSize | undefined;
|
|
86
|
+
private lastAppliedSize: BoxSize | undefined;
|
|
87
|
+
private lastAppliedSource: "observer" | "event" | undefined;
|
|
88
|
+
private offBoxSizeChange: (() => void) | undefined;
|
|
89
|
+
private contentResizeObserver: ResizeObserver | undefined;
|
|
64
90
|
|
|
65
91
|
public constructor({
|
|
66
92
|
context,
|
|
@@ -112,6 +138,7 @@ export class SlideDocsViewer {
|
|
|
112
138
|
});
|
|
113
139
|
|
|
114
140
|
this.render();
|
|
141
|
+
this._registerBoxSizeChange();
|
|
115
142
|
|
|
116
143
|
// 在 render() 之后设置监听器,确保 ResizableContainer 已经创建
|
|
117
144
|
this.sideEffect.add(() => {
|
|
@@ -172,7 +199,19 @@ export class SlideDocsViewer {
|
|
|
172
199
|
|
|
173
200
|
public setJustSildeReadonly(justSildeReadonly: boolean) {
|
|
174
201
|
this.justSildeReadonly = justSildeReadonly;
|
|
175
|
-
this.
|
|
202
|
+
this.applyInteractionState();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public setSyncEventQueuePolicy(policy: SyncEventQueuePolicy) {
|
|
206
|
+
this.syncEventQueuePolicy = policy;
|
|
207
|
+
this.applyInteractionState();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
protected applyInteractionState() {
|
|
211
|
+
const slide = this.slideController?.slide;
|
|
212
|
+
if (!slide) return;
|
|
213
|
+
slide.setInteractive(!this.justSildeReadonly);
|
|
214
|
+
slide.setSyncEventQueuePolicy(this.syncEventQueuePolicy);
|
|
176
215
|
}
|
|
177
216
|
|
|
178
217
|
public render() {
|
|
@@ -181,7 +220,8 @@ export class SlideDocsViewer {
|
|
|
181
220
|
this.resizableContainer = new ResizableContainer(
|
|
182
221
|
this.viewer.$content,
|
|
183
222
|
this.context,
|
|
184
|
-
this.enableScale
|
|
223
|
+
this.enableScale,
|
|
224
|
+
this.box,
|
|
185
225
|
);
|
|
186
226
|
}
|
|
187
227
|
|
|
@@ -261,6 +301,7 @@ export class SlideDocsViewer {
|
|
|
261
301
|
onNavigate: this.onNavigate,
|
|
262
302
|
onError: this.onError,
|
|
263
303
|
});
|
|
304
|
+
this.applyInteractionState();
|
|
264
305
|
|
|
265
306
|
this.resizableContainer.setSlideObject(this.slideController.slide);
|
|
266
307
|
this.scaleDocsToFit();
|
|
@@ -269,9 +310,99 @@ export class SlideDocsViewer {
|
|
|
269
310
|
return () => this.whiteboardView.callbacks.off("onSizeUpdated", this.scaleDocsToFit);
|
|
270
311
|
});
|
|
271
312
|
|
|
313
|
+
this._observeContentSize();
|
|
314
|
+
this._applyCurrentBoxSize();
|
|
315
|
+
this.context.getAppProxy?.()?.scheduleBoxSizeSync?.();
|
|
316
|
+
|
|
272
317
|
return this;
|
|
273
318
|
}
|
|
274
319
|
|
|
320
|
+
private _getContentSize(): BoxSize {
|
|
321
|
+
const rect = (this.viewer.$content || this.box.$content).getBoundingClientRect();
|
|
322
|
+
return { width: rect.width, height: rect.height };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private _applyResize = (size: BoxSize, source: "observer" | "event"): void => {
|
|
326
|
+
if (size.width <= 0 || size.height <= 0) return;
|
|
327
|
+
if (sizesEqual(this.lastAppliedSize, size)) return;
|
|
328
|
+
|
|
329
|
+
this.lastAppliedSize = size;
|
|
330
|
+
this.lastAppliedSource = source;
|
|
331
|
+
this.resizableContainer?.updateResizableContainer();
|
|
332
|
+
this.slideController?.slide?.notifyFrameResize();
|
|
333
|
+
this.scaleDocsToFit();
|
|
334
|
+
|
|
335
|
+
const domSize = this._getContentSize();
|
|
336
|
+
console.log("[app-slide] resize", {
|
|
337
|
+
appId: this.appId,
|
|
338
|
+
source,
|
|
339
|
+
eventCount: this.boxSizeEventCount,
|
|
340
|
+
event: formatBoxSize(size),
|
|
341
|
+
dom: formatBoxSize(domSize),
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
private _applyCurrentBoxSize = (eventSize?: BoxSize): void => {
|
|
346
|
+
const size = eventSize ?? this._getContentSize();
|
|
347
|
+
this._applyResize(size, eventSize ? "event" : "observer");
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
private _onObserverSize = (size: BoxSize): void => {
|
|
351
|
+
if (size.width <= 0 || size.height <= 0) return;
|
|
352
|
+
this.lastObserverSize = size;
|
|
353
|
+
// observer 先到:立刻 resize,不等 650ms 的 boxSizeChange
|
|
354
|
+
this._applyResize(size, "observer");
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
private _reconnectContentObserver(): void {
|
|
358
|
+
const target = this.viewer.$content;
|
|
359
|
+
if (!target || !this.contentResizeObserver) return;
|
|
360
|
+
this.contentResizeObserver.disconnect();
|
|
361
|
+
this.contentResizeObserver.observe(target);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private _observeContentSize(): void {
|
|
365
|
+
if (!this.contentResizeObserver) {
|
|
366
|
+
this.contentResizeObserver = new ResizeObserverImpl(entries => {
|
|
367
|
+
const entry = entries[entries.length - 1];
|
|
368
|
+
const size = entry?.contentRect
|
|
369
|
+
? { width: entry.contentRect.width, height: entry.contentRect.height }
|
|
370
|
+
: this._getContentSize();
|
|
371
|
+
this._onObserverSize(size);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
this._reconnectContentObserver();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private _onContextBoxSizeChange = (payload: { appId: string; width: number; height: number }) => {
|
|
378
|
+
if (payload.appId !== this.context.appId) return;
|
|
379
|
+
|
|
380
|
+
this.boxSizeEventCount += 1;
|
|
381
|
+
this.latestBoxSize = { width: payload.width, height: payload.height };
|
|
382
|
+
|
|
383
|
+
// observer 已经先处理过同一尺寸时,忽略晚到的 event
|
|
384
|
+
if (sizesEqual(this.lastObserverSize, this.latestBoxSize) || sizesEqual(this.lastAppliedSize, this.latestBoxSize)) {
|
|
385
|
+
console.log("[app-slide] boxSizeChange skipped, observer already applied", {
|
|
386
|
+
appId: this.appId,
|
|
387
|
+
eventCount: this.boxSizeEventCount,
|
|
388
|
+
source: this.lastAppliedSource,
|
|
389
|
+
size: formatBoxSize(this.latestBoxSize),
|
|
390
|
+
});
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// observer 比 event 晚到或漏了:用 event 补一次,并重新挂 observer
|
|
395
|
+
this._applyResize(this.latestBoxSize, "event");
|
|
396
|
+
this._reconnectContentObserver();
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
private _registerBoxSizeChange(): void {
|
|
400
|
+
this.offBoxSizeChange = this.context.emitter.on("boxSizeChange", this._onContextBoxSizeChange);
|
|
401
|
+
this._observeContentSize();
|
|
402
|
+
this._applyCurrentBoxSize();
|
|
403
|
+
this.context.getAppProxy?.()?.scheduleBoxSizeSync?.();
|
|
404
|
+
}
|
|
405
|
+
|
|
275
406
|
protected onError = ({ error, index }: { error: Error; index: number }) => {
|
|
276
407
|
this.viewer.setPaused();
|
|
277
408
|
if (this.slideController?.showRenderError) {
|
|
@@ -322,12 +453,16 @@ export class SlideDocsViewer {
|
|
|
322
453
|
}
|
|
323
454
|
|
|
324
455
|
public unmount() {
|
|
456
|
+
this.offBoxSizeChange?.();
|
|
457
|
+
this.offBoxSizeChange = undefined;
|
|
458
|
+
this.contentResizeObserver?.disconnect();
|
|
459
|
+
this.contentResizeObserver = undefined;
|
|
325
460
|
if (this.slideController) {
|
|
326
461
|
this.slideController.destroy();
|
|
327
462
|
this.slideController = null;
|
|
328
463
|
}
|
|
329
464
|
this.viewer.unmount();
|
|
330
|
-
this.resizableContainer.destroy();
|
|
465
|
+
this.resizableContainer.destroy(this.box);
|
|
331
466
|
return this;
|
|
332
467
|
}
|
|
333
468
|
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface AppOptions
|
|
|
54
54
|
| "logger"
|
|
55
55
|
| "enableGlobalClick"
|
|
56
56
|
| "skipActionWhenFrozen"
|
|
57
|
+
| "syncEventQueuePolicy"
|
|
57
58
|
> {
|
|
58
59
|
/** show debug controller */
|
|
59
60
|
debug?: boolean;
|
|
@@ -105,6 +106,7 @@ export interface AppResult {
|
|
|
105
106
|
prevPage: () => boolean;
|
|
106
107
|
jumpToPage: (page: number) => boolean;
|
|
107
108
|
setSildeReadonly: (bol: boolean) => void;
|
|
109
|
+
setSyncEventQueuePolicy: (policy: NonNullable<ISlideConfig["syncEventQueuePolicy"]>) => void;
|
|
108
110
|
onScaleChanged: (cb: (scale: number) => void) => void;
|
|
109
111
|
scaleView: (to: number) => void;
|
|
110
112
|
getViewScale: () => number | undefined;
|
|
@@ -213,9 +215,9 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
213
215
|
},
|
|
214
216
|
});
|
|
215
217
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
const appOptions = context.getAppOptions();
|
|
219
|
+
docsViewer.setSyncEventQueuePolicy(appOptions?.syncEventQueuePolicy ?? "fifo");
|
|
220
|
+
docsViewer.setJustSildeReadonly(appOptions?.justSildeReadonly ?? false);
|
|
219
221
|
|
|
220
222
|
if (import.meta.env.DEV) {
|
|
221
223
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -318,6 +320,9 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
318
320
|
setSildeReadonly: (bol: boolean) => {
|
|
319
321
|
docsViewer?.setJustSildeReadonly(bol);
|
|
320
322
|
},
|
|
323
|
+
setSyncEventQueuePolicy: policy => {
|
|
324
|
+
docsViewer?.setSyncEventQueuePolicy(policy);
|
|
325
|
+
},
|
|
321
326
|
viewer: () => {
|
|
322
327
|
return docsViewer;
|
|
323
328
|
},
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2020 netless
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|