aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CaptureManager.scss2
-rw-r--r--src/client/util/CurrentUserUtils.ts30
-rw-r--r--src/client/util/DocumentManager.ts5
-rw-r--r--src/client/util/DragManager.ts3
-rw-r--r--src/client/util/History.ts28
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts14
-rw-r--r--src/client/util/PingManager.ts43
-rw-r--r--src/client/util/SettingsManager.scss2
-rw-r--r--src/client/util/SettingsManager.tsx3
-rw-r--r--src/client/util/SharingManager.tsx6
-rw-r--r--src/client/util/SnappingManager.ts6
-rw-r--r--src/client/util/bezierFit.ts254
-rw-r--r--src/client/util/node_modules/type_decls.d4
-rw-r--r--src/client/util/reportManager/ReportManager.scss4
-rw-r--r--src/client/util/request-image-size.ts9
15 files changed, 265 insertions, 148 deletions
diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss
index 8679a0101..e7cbb4287 100644
--- a/src/client/util/CaptureManager.scss
+++ b/src/client/util/CaptureManager.scss
@@ -1,5 +1,3 @@
-@import '../views/global/globalCssVariables.module';
-
.capture-interface {
//background-color: whitesmoke !important;
width: 450px;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6ca181d92..8736225d0 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -445,7 +445,6 @@ pie title Minerals in my tap water
{ toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc, clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
{ toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
- // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack />" as any, openFactoryLocation: OpenWhere.overlay},
].map(tuple => (
{ openFactoryLocation: OpenWhere.addRight,
scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)',
@@ -674,12 +673,13 @@ pie title Minerals in my tap water
CurrentUserUtils.createToolButton(opts), scripts, funcs);
const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
- { scripts: { onClick: "undo()"}, opts: { title: "Undo", icon: "undo-alt", toolTip: "Undo ⌘Z" }},
- { scripts: { onClick: "redo()"}, opts: { title: "Redo", icon: "redo-alt", toolTip: "Redo ⌘⇧Z" }},
- { scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
- { scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}},
- { scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
- { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}}
+ { scripts: { onClick: "undo()"}, opts: { title: "Undo", icon: "undo-alt", toolTip: "Undo ⌘Z" }},
+ { scripts: { onClick: "redo()"}, opts: { title: "Redo", icon: "redo-alt", toolTip: "Redo ⌘⇧Z" }},
+ { scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
+ { scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}},
+ { scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
+ { scripts: { onClick: "hideUI()"},opts: { title: "Toggle UI", icon: "portrait", toolTip: "Toggle visibility of UI buttons"}},
+ { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}}
];
const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
const dockBtnsReqdOpts:DocumentOptions = {
@@ -710,11 +710,7 @@ pie title Minerals in my tap water
{ title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
{ title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
{ title: "Tags", icon:"bolt", toolTip:"Sort by document's tags", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"tag", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
- { title: "Sort", icon: "sort" , toolTip: "Manage sort order / lock status", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true,
- subMenu: [
- { title: "Ascending", toolTip: "Sort the cards in ascending order", btnType: ButtonType.ToggleButton, icon: "sort-up", toolType:"up", ignoreClick: true, scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
- { title: "Descending",toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true, scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
- ]},
+ { title: "Reverse", icon: "sort-up", toolTip: "Sort the cards in reverse order", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"reverse", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
]
}
@@ -738,7 +734,7 @@ pie title Minerals in my tap water
}
static viewTools(): Button[] {
return [
- { title: "Show Tags", icon: "id-card", toolTip: "Toggle tags", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"toggle-tags",funcs: { }, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+ { title: "Tags", icon: "id-card", toolTip: "Toggle Tags display", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"toggle-tags",funcs: { }, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
{ title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
{ title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
{ title: "Fit All", icon: "object-group", toolTip:"Fit Docs to View (double tap to persist)",
@@ -830,18 +826,20 @@ pie title Minerals in my tap water
return [
{ btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Card, CollectionViewType.Carousel,CollectionViewType.Carousel3D,
CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear,
- CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Schema, CollectionViewType.Stacking,
+ CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Pivot, CollectionViewType.Schema, CollectionViewType.Stacking,
CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]),
title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}},
{ title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
{ title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
{ title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
{ title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
+ { title: "Border", icon: "pen", toolTip: "Border Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBorderColor(value, _readOnly_)'} }, // Only when a document is selected
+ { title: "B.Width", toolTip: "Border width", btnType: ButtonType.NumberSliderButton, ignoreClick: true, scripts: {script: '{ return setBorderWidth(value, _readOnly_);}'}, numBtnMin: 0, linearBtnWidth:40},
{ title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
{ title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
{ title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
{ title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
- { title: "Chat", icon:"lightbulb", toolTip: "Toggle Chat Assistant",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat", funcs: {}, width: 30, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+ { title: "Chat", icon: "lightbulb", toolTip: "Toggle Chat Assistant",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat", funcs: {}, width: 30, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
{ title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.filterTools(), ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
{ title: "Sort", icon: "Sort", toolTip: "Sort Documents", subMenu: CurrentUserUtils.sortTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
{ title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
@@ -913,7 +911,7 @@ pie title Minerals in my tap water
CurrentUserUtils.createToolButton(opts), scripts, funcs);
const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
- { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}},
+ { opts: { title: "Replicate",icon:"camera",toolTip:"Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}},
{ opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`},scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
{ opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}},
{ opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index acb35f7eb..e33449782 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -354,9 +354,10 @@ export class DocumentManager {
// bcz: should this delay be an options parameter?
setTimeout(() => {
Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
- if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
+ const zoomableText = StrCast(targetDoc.text_html, StrCast(targetDoc.ai_firefly_prompt));
+ if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && zoomableText) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
- contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
+ contextView.setTextHtmlOverlay(zoomableText, options.effect);
DocumentManager._overlayViews.add(contextView);
}
Doc.AddUnHighlightWatcher(() => {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 81ea840f1..2a7859f09 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -555,9 +555,12 @@ export namespace DragManager {
let scrollAwaiter: Opt<NodeJS.Timeout>;
let startWindowDragTimer: NodeJS.Timeout | undefined;
+ const startCanEmbed = SnappingManager.CanEmbed;
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (docDragData) {
+ if (e.ctrlKey) SnappingManager.SetCanEmbed(true);
+ else if (!startCanEmbed) SnappingManager.SetCanEmbed(false);
docDragData.userDropAction = e.ctrlKey && e.altKey ? dropActionType.copy : e.shiftKey ? dropActionType.move : e.ctrlKey ? dropActionType.embed : docDragData.defaultDropAction;
const targClassName = e.target instanceof HTMLElement && typeof e.target.className === 'string' ? e.target.className : '';
if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(targClassName) && docDragData.draggedDocuments.length === 1) {
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 0d0c056a4..9728e3177 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,10 +1,6 @@
/* eslint-disable no-use-before-define */
/* eslint-disable no-empty */
-/* eslint-disable no-continue */
-/* eslint-disable guard-for-in */
-/* eslint-disable no-restricted-syntax */
/* eslint-disable no-param-reassign */
-import * as qs from 'query-string';
import { Doc } from '../../fields/Doc';
import { OmitKeys, ClientUtils } from '../../ClientUtils';
import { DocServer } from '../DocServer';
@@ -82,7 +78,7 @@ export namespace HistoryUtil {
// }
// }
- const parsers: { [type: string]: (pathname: string[], opts: qs.ParsedQuery) => ParsedUrl | undefined } = {};
+ const parsers: { [type: string]: (pathname: string[], opts: URLSearchParams) => ParsedUrl | undefined } = {};
const stringifiers: { [type: string]: (state: ParsedUrl) => string } = {};
type ParserValue = true | 'none' | 'json' | ((value: string) => string | null | (string | null)[]);
@@ -91,8 +87,8 @@ export namespace HistoryUtil {
[key: string]: ParserValue;
};
- function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: qs.ParsedQuery, current: ParsedUrl) => ParsedUrl | null | undefined) {
- function parse(parser: ParserValue, value: string | (string | null)[] | null | undefined) {
+ function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: URLSearchParams, current: ParsedUrl) => ParsedUrl | null | undefined) {
+ function parseValue(parser: ParserValue, value: string | (string | null)[] | null | undefined) {
if (value === undefined || value === null) {
return value;
}
@@ -108,21 +104,21 @@ export namespace HistoryUtil {
parsers[type] = (pathname, opts) => {
const current: DocUrl & { [key: string]: null | (string | null)[] | string } = { type: 'doc', docId: '' };
for (const required in requiredFields) {
- if (!(required in opts)) {
+ if (!opts.has(required)) {
return undefined;
}
const parser = requiredFields[required];
- const value = parse(parser, opts[required]);
+ const value = parseValue(parser, opts.get(required));
if (value !== null && value !== undefined) {
current[required] = value;
}
}
for (const opt in optionalFields) {
- if (!(opt in opts)) {
+ if (!opts.has(opt)) {
continue;
}
const parser = optionalFields[opt];
- const value = parse(parser, opts[opt]);
+ const value = parseValue(parser, opts.get(opt));
if (value !== undefined) {
current[opt] = value;
}
@@ -148,12 +144,12 @@ export namespace HistoryUtil {
path = customStringifier(state, path);
}
const queryObj = OmitKeys(state, keys).extract;
- const query: { [key: string]: string | null } = {};
+ const query = new URLSearchParams();
Object.keys(queryObj).forEach(key => {
- query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]);
+ query.set(key, queryObj[key] === null ? '' : JSON.stringify(queryObj[key]));
});
- const queryString = qs.stringify(query);
- return path + (queryString ? `?${queryString}` : '');
+ const qstr = query.toString();
+ return path + (qstr ? `?${qstr}` : '');
};
}
@@ -170,7 +166,7 @@ export namespace HistoryUtil {
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
const { search } = location;
- const opts = search.length ? qs.parse(search, { sort: false }) : {};
+ const opts = new URLSearchParams(search);
const pathnameSplit = pathname.split('/');
const type = pathnameSplit[0];
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 8d4eefa7e..43807397f 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -4,22 +4,16 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
+import { Upload } from '../../../server/SharedMediaTypes';
import { Networking } from '../../Network';
export namespace ImageUtils {
- export type imgInfo = {
- contentSize: number;
- nativeWidth: number;
- nativeHeight: number;
- source: string;
- exifData: { error: string | undefined; data: string };
- };
- export const ExtractImgInfo = async (document: Doc): Promise<imgInfo | undefined> => {
+ export const ExtractImgInfo = async (document: Doc) => {
const field = Cast(document.data, ImageField);
- return field ? Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
+ return field ? (Networking.PostToServer('/inspectImage', { source: field.url.href }) as Promise<Upload.InspectionResults>) : undefined;
};
- export const AssignImgInfo = (document: Doc, data?: imgInfo) => {
+ export const AssignImgInfo = (document: Doc, data?: Upload.InspectionResults) => {
if (data) {
data.nativeWidth && (document._height = (NumCast(document._width) * data.nativeHeight) / data.nativeWidth);
const proto = document[DocData];
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 255e9cee0..0e4f8cab0 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,44 +1,37 @@
-import { action, makeObservable, observable, runInAction } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { Networking } from '../Network';
import { SnappingManager } from './SnappingManager';
export class PingManager {
+ PING_INTERVAL_SECONDS = 1;
+ // not used now, but may need to clear interval
+ private _interval: NodeJS.Timeout | null = null;
// create static instance and getter for global use
// eslint-disable-next-line no-use-before-define
- @observable static _instance: PingManager;
+ @observable private static _instance: PingManager;
@observable IsBeating = true;
static get Instance(): PingManager {
return PingManager._instance;
}
- // not used now, but may need to clear interval
- private _interval: NodeJS.Timeout | null = null;
- INTERVAL_SECONDS = 1;
constructor() {
makeObservable(this);
PingManager._instance = this;
- this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
+ this._interval = setInterval(this.sendPing, this.PING_INTERVAL_SECONDS * 1000);
}
- private setIsBeating = action((status: boolean) => {
- this.IsBeating = status;
- setTimeout(this.showAlert, 100);
- });
+ showAlert = () => alert(PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.');
- showAlert = () => {
- alert(PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.');
- };
- sendPing = async (): Promise<void> => {
- try {
- const res = await Networking.PostToServer('/ping', { date: new Date() });
- runInAction(() => {
- SnappingManager.SetServerVersion(res.message);
- });
- !this.IsBeating && this.setIsBeating(true);
- } catch {
- if (this.IsBeating) {
- this.setIsBeating(false);
- }
- }
+ sendPing = () => {
+ const setIsBeating = action((status: boolean) => {
+ this.IsBeating = status;
+ setTimeout(this.showAlert, 100);
+ });
+ Networking.PostToServer('/ping', { date: new Date() })
+ .then(res => {
+ SnappingManager.SetServerVersion((res as { message: string }).message);
+ !this.IsBeating && setIsBeating(true);
+ })
+ .catch(() => this.IsBeating && setIsBeating(false));
};
}
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index dbfc48c63..f81f17589 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -1,5 +1,3 @@
-@import '../views/global/globalCssVariables.module';
-
.settings-interface {
//background-color: whitesmoke !important;
width: 450px;
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 5d041f7b4..6ea242fc3 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -598,7 +598,8 @@ export class SettingsManager extends React.Component<object> {
});
} else {
const passwordBundle = { curr_pass: this._curr_password, new_pass: this._new_password, new_confirm: this._new_confirm };
- const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ const reset = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ const { error } = reset as { error: { msg: string }[] };
runInAction(() => {
this._passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!';
});
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index efc8e79a6..3a248400b 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -502,7 +502,6 @@ export class SharingManager extends React.Component<object> {
}
};
- // eslint-disable-next-line react/sort-comp
public close = action(() => {
this.isOpen = false;
this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
@@ -517,7 +516,6 @@ export class SharingManager extends React.Component<object> {
this.layoutDocAcls = false;
});
- // eslint-disable-next-line react/no-unused-class-component-methods
public open = (target?: DocumentView, targetDoc?: Doc) => {
this.populateUsers();
runInAction(() => {
@@ -534,7 +532,6 @@ export class SharingManager extends React.Component<object> {
* @param group
* @param emailId
*/
- // eslint-disable-next-line react/no-unused-class-component-methods
shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
const user = this.users.find(({ user: { email } }) => email === emailId)!;
if (group.docsShared) {
@@ -559,7 +556,6 @@ export class SharingManager extends React.Component<object> {
/**
* Called from the properties sidebar to change permissions of a user.
*/
- // eslint-disable-next-line react/no-unused-class-component-methods
shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
if (layout) this.layoutDocAcls = true;
if (shareWith !== 'Guest') {
@@ -583,7 +579,6 @@ export class SharingManager extends React.Component<object> {
* @param group
* @param emailId
*/
- // eslint-disable-next-line react/no-unused-class-component-methods
removeMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
@@ -607,7 +602,6 @@ export class SharingManager extends React.Component<object> {
* Removes a group's permissions from documents that have been shared with it.
* @param group
*/
- // eslint-disable-next-line react/no-unused-class-component-methods
removeGroup = (group: Doc) => {
if (group.docsShared) {
DocListCast(group.docsShared).forEach(doc => {
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 2a150dc5a..3bbc297b8 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -16,6 +16,7 @@ export class SnappingManager {
@observable _shiftKey = false;
@observable _ctrlKey = false;
@observable _metaKey = false;
+ @observable _hideUI = false;
@observable _showPresPaths = false;
@observable _isLinkFollowing = false;
@observable _isDragging: boolean = false;
@@ -32,6 +33,7 @@ export class SnappingManager {
@observable _hideDecorations: boolean = false;
@observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode
@observable _inkShape: Gestures | undefined = undefined;
+ @observable _chatVisible: boolean = false;
private constructor() {
SnappingManager._manager = this;
@@ -52,6 +54,7 @@ export class SnappingManager {
public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore
public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore
public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore
+ public static get HideUI() { return this.Instance._hideUI; } // prettier-ignore
public static get ShowPresPaths() { return this.Instance._showPresPaths; } // prettier-ignore
public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore
@@ -66,11 +69,13 @@ export class SnappingManager {
public static get HideDecorations(){ return this.Instance._hideDecorations; } // prettier-ignore
public static get KeepGestureMode(){ return this.Instance._keepGestureMode; } // prettier-ignore
public static get InkShape() { return this.Instance._inkShape; } // prettier-ignore
+ public static get ChatVisible() { return this.Instance._chatVisible; } // prettier-ignore
public static SetLongPress = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore
public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
+ public static SetHideUI = (vis: boolean) => runInAction(() => {this.Instance._hideUI = vis}); // prettier-ignore
public static SetShowPresPaths = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore
public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
public static SetIsDragging = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
@@ -85,6 +90,7 @@ export class SnappingManager {
public static SetHideDecorations= (state:boolean) =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore
public static SetKeepGestureMode= (state:boolean) =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore
public static SetInkShape = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore
+ public static SetChatVisible = (vis:boolean) =>runInAction(() => {this.Instance._chatVisible = vis}); // prettier-ignore
public static userColor: string | undefined;
public static userVariantColor: string | undefined;
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 7ef370d48..0399fe1d5 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,8 +1,7 @@
/* eslint-disable no-use-before-define */
-/* eslint-disable prefer-destructuring */
/* eslint-disable no-param-reassign */
-/* eslint-disable camelcase */
import { Point } from '../../pen-gestures/ndollar';
+import { numberRange } from '../../Utils';
export enum SVGType {
Rect = 'rect',
@@ -625,13 +624,132 @@ export function GenerateControlPoints(coordinates: Point[], alpha = 0.1) {
return [...firstEnd, ...points, ...lastEnd];
}
-export function SVGToBezier(name: SVGType, attributes: any): Point[] {
+function convertRelativePathCmdsToAbsolute(pathData: string): string {
+ const commands = pathData.match(/[a-zA-Z][^a-zA-Z]*/g);
+ let currentX = 0;
+ let currentY = 0;
+ let startX = 0;
+ let startY = 0;
+ const absoluteCommands = commands?.map(command => {
+ const values = command
+ .slice(1)
+ .trim()
+ .split(/[\s,]+/)
+ .map(v => +v);
+
+ switch (command[0]) {
+ case 'M':
+ currentX = values[0];
+ currentY = values[1];
+ startX = currentX;
+ startY = currentY;
+ return `M${currentX},${currentY}`;
+ case 'm':
+ currentX += values[0];
+ currentY += values[1];
+ startX = currentX;
+ startY = currentY;
+ return `M${currentX},${currentY}`;
+ case 'L':
+ currentX = values[values.length - 2];
+ currentY = values[values.length - 1];
+ return `L${values.join(',')}`;
+ case 'l': {
+ let str = '';
+ for (let i = 0; i < values.length; i += 2) {
+ str += (i === 0 ? 'L':',') + (values[i] + currentX) +
+ ',' + (values[i + 1] + currentY); // prettier-ignore
+ currentX += values[i];
+ currentY += values[i + 1];
+ }
+ return str;
+ }
+ case 'H':
+ currentX = values[0];
+ return `H${currentX}`;
+ case 'h':
+ currentX += values[0];
+ return `H${currentX}`;
+ case 'V':
+ currentY = values[0];
+ return `V${currentY}`;
+ case 'v':
+ currentY += values[0];
+ return `V${currentY}`;
+ case 'C':
+ currentX = values[values.length - 2];
+ currentY = values[values.length - 1];
+ return `C${values.join(',')}`;
+ case 'c': {
+ let str = '';
+ for (let i = 0; i < values.length; i += 6) {
+ str += (i === 0 ? 'C':',') + (values[i] + currentX) +
+ ',' + (values[i + 1] + currentY) +
+ ',' + (values[i + 2] + currentX) +
+ ',' + (values[i + 3] + currentY) +
+ ',' + (values[i + 4] + currentX) +
+ ',' + (values[i + 5] + currentY); // prettier-ignore
+ currentX += values[i + 4];
+ currentY += values[i + 5];
+ }
+ return str;
+ }
+ case 'S':
+ currentX = values[2];
+ currentY = values[3];
+ return `S${values.join(',')}`;
+ case 's':
+ return `S${values.map((v, i) => (i % 2 === 0 ? (currentX += v) : (currentY += v))).join(',')}`;
+ case 'Q':
+ currentX = values[values.length - 2];
+ currentY = values[values.length - 1];
+ return `Q${values.join(',')}`;
+ case 'q': {
+ let str = '';
+ for (let i = 0; i < values.length; i += 4) {
+ str += (i === 0 ? 'Q':',') + (values[i] + currentX) +
+ ',' + (values[i + 1] + currentY) +
+ ',' + (values[i + 2] + currentX) +
+ ',' + (values[i + 3] + currentY); // prettier-ignore
+ currentX += values[i + 2];
+ currentY += values[i + 3];
+ }
+ return str;
+ }
+ case 'T':
+ currentX = values[0];
+ currentY = values[1];
+ return `T${currentX},${currentY}`;
+ case 't':
+ currentX += values[0];
+ currentY += values[1];
+ return `T${currentX},${currentY}`;
+ case 'A':
+ currentX = values[5];
+ currentY = values[6];
+ return `A${values.join(',')}`;
+ case 'a':
+ return `A${values.map((v, i) => (i % 2 === 0 ? (currentX += v) : (currentY += v))).join(',')}`;
+ case 'Z':
+ case 'z':
+ currentX = startX;
+ currentY = startY;
+ return 'Z';
+ default:
+ return command;
+ }
+ });
+
+ return absoluteCommands?.join(' ') ?? pathData;
+}
+
+export function SVGToBezier(name: SVGType, attributes: Record<string, string>, last: { X: number; Y: number }): Point[] {
switch (name) {
case 'line': {
- const x1 = parseInt(attributes.x1);
- const x2 = parseInt(attributes.x2);
- const y1 = parseInt(attributes.y1);
- const y2 = parseInt(attributes.y2);
+ const x1 = +attributes.x1;
+ const x2 = +attributes.x2;
+ const y1 = +attributes.y1;
+ const y2 = +attributes.y2;
return [
{ X: x1, Y: y1 },
{ X: x1, Y: y1 },
@@ -642,10 +760,10 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
case 'circle':
case 'ellipse': {
const c = 0.551915024494;
- const centerX = parseInt(attributes.cx);
- const centerY = parseInt(attributes.cy);
- const radiusX = parseInt(attributes.rx) || parseInt(attributes.r);
- const radiusY = parseInt(attributes.ry) || parseInt(attributes.r);
+ const centerX = +attributes.cx;
+ const centerY = +attributes.cy;
+ const radiusX = +attributes.rx || +attributes.r;
+ const radiusY = +attributes.ry || +attributes.r;
return [
{ X: centerX, Y: centerY + radiusY },
{ X: centerX + c * radiusX, Y: centerY + radiusY },
@@ -666,10 +784,10 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
];
}
case 'rect': {
- const x = parseInt(attributes.x);
- const y = parseInt(attributes.y);
- const width = parseInt(attributes.width);
- const height = parseInt(attributes.height);
+ const x = +attributes.x;
+ const y = +attributes.y;
+ const width = +attributes.width;
+ const height = +attributes.height;
return [
{ X: x, Y: y },
{ X: x, Y: y },
@@ -690,57 +808,73 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
];
}
case 'path': {
- const coordList: Point[] = [];
- const [startX, startY] = attributes.d.match(/M(-?\d+\.?\d*),(-?\d+\.?\d*)/).slice(1);
- const startPt = { X: parseInt(startX), Y: parseInt(startY) };
- coordList.push(startPt);
- const matches: RegExpMatchArray[] = Array.from(
- attributes.d.matchAll(/Q(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|C(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|L(-?\d+\.?\d*),(-?\d+\.?\d*)/g)
+ const cmds = new Map<string, number>([
+ ['A', 7],
+ ['C', 6],
+ ['Q', 4],
+ ['L', 2],
+ ['V', 1],
+ ['H', 1],
+ ['Z', 0],
+ ['M', 2],
+ ]);
+ const cmdReg = (letter: string) => `${letter}?${numberRange(cmds.get(letter)??0).map(() => '[, ]?(-?\\d*\\.?\\d*)').join('')}`; // prettier-ignore
+ const pathdata = convertRelativePathCmdsToAbsolute(
+ attributes.d
+ .replace(/([0-9])-/g, '$1,-') // numbers are smooshed together - put a ',' between number-number => number,-number
+ .replace(/([.][0-9]+)(?=\.)/g, '$1,') // numbers are smooshed together - put a ',' between .number.number => .number,.number
+ .trim()
);
- let lastPt: Point = startPt;
- matches.forEach(match => {
- if (match[0].startsWith('Q')) {
- coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
- coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
- coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
- coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
- lastPt = { X: parseInt(match[3]), Y: parseInt(match[4]) };
- } else if (match[0].startsWith('C')) {
- coordList.push({ X: parseInt(match[5]), Y: parseInt(match[6]) });
- coordList.push({ X: parseInt(match[7]), Y: parseInt(match[8]) });
- coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) });
- coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) });
- lastPt = { X: parseInt(match[9]), Y: parseInt(match[10]) };
- } else {
- coordList.push(lastPt);
- coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
- coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
- coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
- lastPt = { X: parseInt(match[11]), Y: parseInt(match[12]) };
- }
- });
- const hasZ = attributes.d.match(/Z/);
- if (hasZ || attributes.fill) {
- coordList.push(lastPt);
- coordList.push(startPt);
- coordList.push(startPt);
- } else {
- coordList.pop();
- }
+ const move = pathdata.match(cmdReg('M'));
+ const start = move?.slice(1).map(v => +v) ?? [last.X, last.Y];
+ const coordList: Point[] = [];
+ for (let prev = coordList.lastElement() ?? { X: start[0], Y: start[1] },
+ pathcmd = pathdata.slice(move?.[0].length ?? 0).trim(),
+ m = move,
+ lastCmd = '';
+ pathcmd;
+ pathcmd = pathcmd.slice(m?.[0].length ?? 1).trim(),
+ prev = coordList.lastElement()
+ ) {
+ lastCmd = Array.from(cmds.keys()).includes(pathcmd[0]) ? pathcmd[0] : lastCmd; // command character is first, otherwise we're continuing coordinates for the last command
+ m = pathcmd.match(new RegExp(cmdReg(lastCmd)))!; // matches command + number parameters specific to command
+ switch (m ? lastCmd : 'error') {
+ case 'Q': // convert quadratic to Bezier
+ ((Q) => coordList.push(
+ prev,
+ { X: prev.X + (2 / 3) * (Q[0] - prev.X), Y: prev.Y + (2 / 3) * (Q[1] - prev.Y) },
+ { X: Q[2] + (2 / 3) * (Q[0] - Q[2]), Y: Q[3] + (2 / 3) * (Q[1] - Q[3]) },
+ { X: Q[2], Y: Q[3] }
+ ))([+m[1], +m[2], +m[3], +m[4]]);
+ break; case 'C': // bezier curve
+ coordList.push(prev, { X: +m[1], Y: +m[2] }, { X: +m[3], Y: +m[4] }, { X: +m[5], Y: +m[6] });
+ break; case 'L': // convert line to bezier
+ coordList.push(prev, prev, { X: +m[1], Y: +m[2] }, { X: +m[1], Y: +m[2] });
+ break; case 'H': // convert horiz line to bezier
+ coordList.push(prev, prev, { X: +m[1], Y: prev.Y }, { X: +m[1], Y: prev.Y });
+ break; case 'V': // convert vert line to bezier
+ coordList.push(prev, prev, { X: prev.X, Y: +m[1] }, { X: prev.X, Y: +m[1] });
+ break; case 'A': // convert arc to bezier
+ console.log('SKIPPING arc - conversion to bezier not implemented');
+ break; case 'Z':
+ break;
+ default:
+ // eslint-disable-next-line no-debugger
+ debugger;
+ } // prettier-ignore
+ } // prettier-ignore
return coordList;
}
case 'polygon': {
- const coords: RegExpMatchArray[] = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g));
- let list: Point[] = [];
+ const coords = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g));
+ const list: Point[] = [];
coords.forEach(coord => {
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
+ list.push({ X: +coord[1], Y: +coord[2] });
+ list.push({ X: +coord[1], Y: +coord[2] });
+ list.push({ X: +coord[1], Y: +coord[2] });
+ list.push({ X: +coord[1], Y: +coord[2] });
});
- const firstPts = list.splice(0, 2);
- list = list.concat(firstPts);
- return list;
+ return list.concat(list.splice(0, 2)); // repeat start point to close
}
default:
return [];
diff --git a/src/client/util/node_modules/type_decls.d b/src/client/util/node_modules/type_decls.d
index 9058b4394..8605b9f5b 100644
--- a/src/client/util/node_modules/type_decls.d
+++ b/src/client/util/node_modules/type_decls.d
@@ -67,8 +67,9 @@ interface RegExp {
readonly sticky: boolean;
readonly unicode: boolean;
}
-interface Date {
+declare class Date {
now() : string;
+ constructor(date:string);
}
interface String {
codePointAt(pos: number): number | undefined;
@@ -170,6 +171,7 @@ declare class VideoField extends URLField { [Copy](): ObjectField; }
declare class ImageField extends URLField { [Copy](): ObjectField; }
declare class WebField extends URLField { [Copy](): ObjectField; }
declare class PdfField extends URLField { [Copy](): ObjectField; }
+declare class DateField extends URLField { [Copy](): ObjectField; constructor(date:Date); }
declare const ComputedField: any;
declare const CompileScript: any;
diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
index fd343ac8e..806741c22 100644
--- a/src/client/util/reportManager/ReportManager.scss
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -1,4 +1,4 @@
-@import '../../views/global/globalCssVariables.module';
+@use '../../views/global/globalCssVariables.module' as global;
// header
@@ -97,7 +97,7 @@
background: transparent;
// &:hover {
- // // border-bottom-color: $text-gray;
+ // // border-bottom-color: global.$text-gray;
// }
// &:focus {
// // border-bottom-color: #4476f7;
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index c619192ed..32ab23618 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
/**
* request-image-size: Detect image dimensions via request.
* Licensed under the MIT license.
@@ -11,12 +10,11 @@
*/
// const imageSize = require('image-size');
-const HttpError = require('standard-http-error');
+import * as HttpError from 'standard-http-error';
import * as request from 'request';
import { imageSize } from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
-
-module.exports = function requestImageSize(url: string) {
+export function requestImageSize(url: string): Promise<ISizeCalculationResult> {
if (!url) {
return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
}
@@ -60,4 +58,5 @@ module.exports = function requestImageSize(url: string) {
req.on('error', reject);
});
-};
+}
+export default requestImageSize;