diff options
-rw-r--r-- | package-lock.json | 58 | ||||
-rw-r--r-- | package.json | 9 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 30 | ||||
-rw-r--r-- | src/client/util/DocumentManager.ts | 2 | ||||
-rw-r--r-- | src/client/util/convertToCSSPTValue.js | 12 | ||||
-rw-r--r-- | src/client/util/jsx-decl.d.ts | 1 | ||||
-rw-r--r-- | src/client/views/OCRUtils.ts | 7 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MarkerIcons.tsx | 109 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/audio/AudioWaveform.tsx | 1 | ||||
-rw-r--r-- | src/server/ApiManagers/SearchManager.ts | 4 | ||||
-rw-r--r-- | src/server/ApiManagers/UploadManager.ts | 50 | ||||
-rw-r--r-- | src/server/DashStats.ts | 150 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 31 | ||||
-rw-r--r-- | src/typings/index.d.ts | 8 |
18 files changed, 252 insertions, 231 deletions
diff --git a/package-lock.json b/package-lock.json index dce4955bb..cdeefa8c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,15 +33,19 @@ "@react-google-maps/api": "^2.19.2", "@turf/turf": "^6.5.0", "@types/bezier-js": "^4.1.3", + "@types/brotli": "^1.3.4", "@types/cors": "^2.8.17", "@types/d3-axis": "^3.0.6", "@types/d3-color": "^3.1.3", "@types/d3-scale": "^4.0.8", "@types/d3-selection": "^3.0.10", "@types/dom-speech-recognition": "0.0.4", + "@types/find-in-files": "^0.5.3", + "@types/fluent-ffmpeg": "^2.1.24", "@types/formidable": "3.4.5", "@types/google-maps": "^3.2.6", "@types/mapbox-gl": "^2.7.19", + "@types/pdf-parse": "^1.1.4", "@types/reveal": "^4.2.0", "@types/supercluster": "^7.1.3", "@types/web": "^0.0.131", @@ -226,18 +230,17 @@ "@types/bcrypt-nodejs": "0.0.31", "@types/bluebird": "^3.5.42", "@types/body-parser": "^1.19.5", - "@types/brotli": "^1.3.4", "@types/chai": "^4.3.11", "@types/color": "^3.0.6", - "@types/connect-flash": "0.0.40", "@types/cookie-parser": "^1.4.6", "@types/cookie-session": "^2.0.48", "@types/d3": "^7.4.3", "@types/exif": "^0.6.5", "@types/express": "^4.17.21", - "@types/express-flash": "0.0.5", "@types/express-session": "^1.17.10", "@types/file-saver": "^2.0.7", + "@types/howler": "^2.2.11", + "@types/html-to-text": "^9.0.4", "@types/jquery": "^3.5.29", "@types/libxmljs": "^0.18.12", "@types/lodash": "^4.14.202", @@ -8662,7 +8665,6 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", "integrity": "sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -8723,15 +8725,6 @@ "@types/node": "*" } }, - "node_modules/@types/connect-flash": { - "version": "0.0.40", - "resolved": "https://registry.npmjs.org/@types/connect-flash/-/connect-flash-0.0.40.tgz", - "integrity": "sha512-vqGDzZ85Kyu/tKdDwXP6JCz4i2Xp3o4bYHSCXbF7XiL1HohogtGXG5pgbgypVbdO3DYqCOHIiZhp2Gh5fP2dDw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/connect-history-api-fallback": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", @@ -9081,15 +9074,6 @@ "@types/serve-static": "*" } }, - "node_modules/@types/express-flash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@types/express-flash/-/express-flash-0.0.5.tgz", - "integrity": "sha512-lz8xxkEev6JHEyHeDNb45tUkhUnPERim9td+Ov7kBDCq6+dFHlu+BvTKYz7DcLh02a3ZK+tg2mze4tZ8DgJyLw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/express-serve-static-core": { "version": "4.17.41", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", @@ -9117,6 +9101,19 @@ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", "dev": true }, + "node_modules/@types/find-in-files": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/find-in-files/-/find-in-files-0.5.3.tgz", + "integrity": "sha512-IGKtSn0Lonfx3HdK6KMcfd5GUc1xdeLtjW1n7ZSA5Tmn1n2gj878q6IC0s4MbF9KtBpXIRqjRQxBzi2kF4WvGw==" + }, + "node_modules/@types/fluent-ffmpeg": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.24.tgz", + "integrity": "sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/formidable": { "version": "3.4.5", "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.5.tgz", @@ -9161,11 +9158,23 @@ "@types/unist": "*" } }, + "node_modules/@types/howler": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.11.tgz", + "integrity": "sha512-7aBoUL6RbSIrqKnpEgfa1wSNUBK06mn08siP2QI0zYk7MXfEJAaORc4tohamQYqCqVESoDyRWSdQn2BOKWj2Qw==", + "dev": true + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" }, + "node_modules/@types/html-to-text": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/html-to-text/-/html-to-text-9.0.4.tgz", + "integrity": "sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==", + "dev": true + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -9400,6 +9409,11 @@ "@types/passport": "*" } }, + "node_modules/@types/pdf-parse": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.4.tgz", + "integrity": "sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg==" + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", diff --git a/package.json b/package.json index 4176593c5..f16b16e7b 100644 --- a/package.json +++ b/package.json @@ -27,18 +27,17 @@ "@types/bcrypt-nodejs": "0.0.31", "@types/bluebird": "^3.5.42", "@types/body-parser": "^1.19.5", - "@types/brotli": "^1.3.4", "@types/chai": "^4.3.11", "@types/color": "^3.0.6", - "@types/connect-flash": "0.0.40", "@types/cookie-parser": "^1.4.6", "@types/cookie-session": "^2.0.48", "@types/d3": "^7.4.3", "@types/exif": "^0.6.5", "@types/express": "^4.17.21", - "@types/express-flash": "0.0.5", "@types/express-session": "^1.17.10", "@types/file-saver": "^2.0.7", + "@types/howler": "^2.2.11", + "@types/html-to-text": "^9.0.4", "@types/jquery": "^3.5.29", "@types/libxmljs": "^0.18.12", "@types/lodash": "^4.14.202", @@ -117,15 +116,19 @@ "@react-google-maps/api": "^2.19.2", "@turf/turf": "^6.5.0", "@types/bezier-js": "^4.1.3", + "@types/brotli": "^1.3.4", "@types/cors": "^2.8.17", "@types/d3-axis": "^3.0.6", "@types/d3-color": "^3.1.3", "@types/d3-scale": "^4.0.8", "@types/d3-selection": "^3.0.10", "@types/dom-speech-recognition": "0.0.4", + "@types/find-in-files": "^0.5.3", + "@types/fluent-ffmpeg": "^2.1.24", "@types/formidable": "3.4.5", "@types/google-maps": "^3.2.6", "@types/mapbox-gl": "^2.7.19", + "@types/pdf-parse": "^1.1.4", "@types/reveal": "^4.2.0", "@types/supercluster": "^7.1.3", "@types/web": "^0.0.131", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ff14eb101..0a4d3a294 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -59,9 +59,7 @@ import { WebBox } from '../views/nodes/WebBox'; import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox'; -const { - default: { DFLT_IMAGE_NATIVE_DIM }, -} = require('../views/global/globalCssVariables.module.scss'); +const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); class EmptyBox { @@ -179,7 +177,7 @@ export class DocumentOptions { longitude?: NUMt = new NumInfo('longitude coordinate for map views', false); routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordinates markerType?: STRt = new StrInfo('Defines the marker type for a pushpin document'); - markerColor?: STRt= new StrInfo('Defines the marker color for a pushpin document'); + markerColor?: STRt = new StrInfo('Defines the marker color for a pushpin document'); map?: STRt = new StrInfo('text location of map'); map_type?: STRt = new StrInfo('type of map view', false); map_zoom?: NUMt = new NumInfo('zoom of a map view', false); @@ -790,8 +788,8 @@ export namespace Docs { { layout: { view: CalendarBox, dataField: defaultDataKey }, options: {}, - } - ] + }, + ], ]); const suffix = 'Proto'; @@ -1032,7 +1030,6 @@ export namespace Docs { export function LoadingDocument(file: File | string, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, ''); } - export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); @@ -1139,14 +1136,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MAP), new List(documents), options); } - export function PushpinDocument( - latitude: number, - longitude: number, - infoWindowOpen: boolean, - documents: Array<Doc>, - options: DocumentOptions, - id?: string) { - + export function PushpinDocument(latitude: number, longitude: number, infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude, longitude, infoWindowOpen, ...options }, id); } @@ -1154,8 +1144,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), { infoWindowOpen, ...options }, id); } - export function CalendarDocument(options: DocumentOptions, documents: Array<Doc>){ - return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), {...options}) + export function CalendarDocument(options: DocumentOptions, documents: Array<Doc>) { + return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), { ...options }); } // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView) @@ -1212,8 +1202,8 @@ export namespace Docs { return doc; } - export function CalendarCollectionDocument(documents: Array<Doc>, options: DocumentOptions){ - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), {...options, _type_collection: CollectionViewType.Calendar}); + export function CalendarCollectionDocument(documents: Array<Doc>, options: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Calendar }); } export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) { @@ -2022,4 +2012,4 @@ ScriptingGlobals.add(function generateLinkTitle(link: Doc) { const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>'; const relation = link.link_relationship || 'to'; return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`; -});
\ No newline at end of file +}); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4816f3317..0101c2bcb 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -16,7 +16,7 @@ import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { PresBox } from '../views/nodes/trails'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; -const { Howl } = require('howler'); +import { Howl } from 'howler'; export class DocumentManager { private static _instance: DocumentManager; diff --git a/src/client/util/convertToCSSPTValue.js b/src/client/util/convertToCSSPTValue.js index 179557953..66f8db5a1 100644 --- a/src/client/util/convertToCSSPTValue.js +++ b/src/client/util/convertToCSSPTValue.js @@ -1,18 +1,16 @@ 'use strict'; -Object.defineProperty(exports, "__esModule", { - value: true +Object.defineProperty(exports, '__esModule', { + value: true, }); exports.PT_TO_PX_RATIO = exports.PX_TO_PT_RATIO = undefined; exports.default = convertToCSSPTValue; exports.toClosestFontPtSize = toClosestFontPtSize; -// var _FontSizeCommandMenuButton = require('./ui/FontSizeCommandMenuButton'); - var SIZE_PATTERN = /([\d\.]+)(px|pt)/i; -var PX_TO_PT_RATIO = exports.PX_TO_PT_RATIO = 0.7518796992481203; // 1 / 1.33. -var PT_TO_PX_RATIO = exports.PT_TO_PX_RATIO = 1.33; +var PX_TO_PT_RATIO = (exports.PX_TO_PT_RATIO = 0.7518796992481203); // 1 / 1.33. +var PT_TO_PX_RATIO = (exports.PT_TO_PX_RATIO = 1.33); function convertToCSSPTValue(styleValue) { var matches = styleValue.match(SIZE_PATTERN); @@ -40,4 +38,4 @@ function toClosestFontPtSize(styleValue) { return _FontSizeCommandMenuButton.FONT_PT_SIZES.reduce(function (prev, curr) { return Math.abs(curr - originalPTValue) < Math.abs(prev - originalPTValue) ? curr : prev; }, Number.NEGATIVE_INFINITY); -}
\ No newline at end of file +} diff --git a/src/client/util/jsx-decl.d.ts b/src/client/util/jsx-decl.d.ts deleted file mode 100644 index 532f06178..000000000 --- a/src/client/util/jsx-decl.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'react-jsx-parser'; diff --git a/src/client/views/OCRUtils.ts b/src/client/views/OCRUtils.ts deleted file mode 100644 index 282ec770e..000000000 --- a/src/client/views/OCRUtils.ts +++ /dev/null @@ -1,7 +0,0 @@ -// import tesseract from "node-tesseract-ocr"; -// const tesseract = require("node-tesseract"); - - -export namespace OCRUtils { - -} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 0237ec95e..0656843cb 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -28,13 +28,11 @@ import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionGridView } from './collectionGrid/CollectionGridView'; -import { CollectionCalendarView} from './CollectionCalendarView'; +import { CollectionCalendarView } from './CollectionCalendarView'; import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; -const path = require('path'); - interface CollectionViewProps_ extends FieldViewProps { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 5b2bf4774..fa472312e 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -47,8 +47,7 @@ import { WebBox } from './WebBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { ImportElementBox } from './importBox/ImportElementBox'; import { PresBox } from './trails/PresBox'; - -const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? +import JsxParser from 'react-jsx-parser'; type BindingProps = Without<FieldViewProps, 'fieldKey'>; export interface JsxBindings { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2d5f68a69..2752fa7f5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -53,7 +53,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; -const { Howl } = require('howler'); +import { Howl } from 'howler'; interface Window { MediaRecorder: MediaRecorder; diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx index 146f296c1..a580fcaa0 100644 --- a/src/client/views/nodes/MapBox/MarkerIcons.tsx +++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx @@ -1,11 +1,42 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faShopify } from '@fortawesome/free-brands-svg-icons'; -import { faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faLocationDot, faLocationPin, faMapPin, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons'; +import { + faBasketball, + faBicycle, + faBowlFood, + faBus, + faCameraRetro, + faCar, + faCartShopping, + faFilm, + faFootball, + faFutbol, + faHockeyPuck, + faHospital, + faHotel, + faHouse, + faLandmark, + faLocationDot, + faLocationPin, + faMapPin, + faMasksTheater, + faMugSaucer, + faPersonHiking, + faPlane, + faSchool, + faShirt, + faShop, + faSquareParking, + faStar, + faTrainSubway, + faTree, + faUtensils, + faVolleyball, +} from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React = require('react'); +import * as React from 'react'; export class MarkerIcons { - // static getMapboxIcon = (color: string) => { // return ( // <svg xmlns="http://www.w3.org/2000/svg" id="marker" data-name="marker" width="20" height="48" viewBox="0 0 20 35"> @@ -34,43 +65,39 @@ export class MarkerIcons { iconProps.color = color; } - return (<FontAwesomeIcon {...iconProps} size={size} />); - - - } - - static FAMarkerIconsMap: {[key: string]: IconProp} = { - 'MAP_PIN': faLocationDot, - 'RESTAURANT_ICON': faUtensils, - 'HOTEL_ICON': faHotel, - 'HOUSE_ICON': faHouse, - 'AIRPLANE_ICON': faPlane, - 'CAR_ICON': faCar, - 'BUS_ICON': faBus, - 'TRAIN_ICON': faTrainSubway, - 'BICYCLE_ICON': faBicycle, - 'PARKING_ICON': faSquareParking, - 'PHOTO_ICON': faCameraRetro, - 'CAFE_ICON': faMugSaucer, - 'STAR_ICON': faStar, - 'SHOPPING_CART_ICON': faCartShopping, - 'SHOPIFY_ICON': faShopify, - 'SHOP_ICON': faShop, - 'SHIRT_ICON': faShirt, - 'FOOD_ICON': faBowlFood, - 'LANDMARK_ICON': faLandmark, - 'HOSPITAL_ICON': faHospital, - 'NATURE_ICON': faTree, - 'HIKING_ICON': faPersonHiking, - 'SOCCER_ICON': faFutbol, - 'VOLLEYBALL_ICON': faVolleyball, - 'BASKETBALL_ICON': faBasketball, - 'HOCKEY_ICON': faHockeyPuck, - 'FOOTBALL_ICON': faFootball, - 'SCHOOL_ICON': faSchool, - 'THEATER_ICON': faMasksTheater, - 'FILM_ICON': faFilm + return <FontAwesomeIcon {...iconProps} size={size} />; } - -}
\ No newline at end of file + static FAMarkerIconsMap: { [key: string]: IconProp } = { + MAP_PIN: faLocationDot, + RESTAURANT_ICON: faUtensils, + HOTEL_ICON: faHotel, + HOUSE_ICON: faHouse, + AIRPLANE_ICON: faPlane, + CAR_ICON: faCar, + BUS_ICON: faBus, + TRAIN_ICON: faTrainSubway, + BICYCLE_ICON: faBicycle, + PARKING_ICON: faSquareParking, + PHOTO_ICON: faCameraRetro, + CAFE_ICON: faMugSaucer, + STAR_ICON: faStar, + SHOPPING_CART_ICON: faCartShopping, + SHOPIFY_ICON: faShopify, + SHOP_ICON: faShop, + SHIRT_ICON: faShirt, + FOOD_ICON: faBowlFood, + LANDMARK_ICON: faLandmark, + HOSPITAL_ICON: faHospital, + NATURE_ICON: faTree, + HIKING_ICON: faPersonHiking, + SOCCER_ICON: faFutbol, + VOLLEYBALL_ICON: faVolleyball, + BASKETBALL_ICON: faBasketball, + HOCKEY_ICON: faHockeyPuck, + FOOTBALL_ICON: faFootball, + SCHOOL_ICON: faSchool, + THEATER_ICON: faMasksTheater, + FILM_ICON: faFilm, + }; +} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 57045e2af..2522a674d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,4 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { htmlToText } from 'html-to-text'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -39,7 +40,6 @@ import { PinProps, PresBox } from './trails'; import './WebBox.scss'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; -const htmlToText = require('html-to-text'); @observer export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { public static LayoutString(fieldKey: string) { diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx index 01392c4a5..c034d95ea 100644 --- a/src/client/views/nodes/audio/AudioWaveform.tsx +++ b/src/client/views/nodes/audio/AudioWaveform.tsx @@ -16,7 +16,6 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; * AudioWaveform * * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents. - * Uses react-audio-waveform package. * Bins the audio data into audioBuckets which are passed to package to render the lines. * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField) */ diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index 186f0bcd3..72c01def7 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -8,7 +8,7 @@ import RouteSubscriber from '../RouteSubscriber'; import { Search } from '../Search'; import ApiManager, { Registration } from './ApiManager'; import { Directory, pathToDirectory } from './UploadManager'; -const findInFiles = require('find-in-files'); +import { find } from 'find-in-files'; export class SearchManager extends ApiManager { protected initialize(register: Registration): void { @@ -47,7 +47,7 @@ export class SearchManager extends ApiManager { const dir = pathToDirectory(Directory.text); try { const regex = new RegExp(q.toString()); - results = await findInFiles.find({ term: q, flags: 'ig' }, dir, '.txt$'); + results = await find({ term: q, flags: 'ig' }, dir, '.txt$'); for (const result in results) { resObj.ids.push(path.basename(result, '.txt').replace(/upload_/, '')); resObj.lines.push(results[result].line); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 06f4f1a9d..8a2fe1389 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -14,8 +14,8 @@ import { SolrManager } from './SearchManager'; import * as uuid from 'uuid'; import { DashVersion } from '../../fields/DocSymbols'; import * as AdmZip from 'adm-zip'; -const imageDataUri = require('image-data-uri'); -const fs = require('fs'); +import * as imageDataUri from 'image-data-uri'; +import * as fs from 'fs'; export enum Directory { parsed_files = 'parsed_files', @@ -265,7 +265,7 @@ export default class UploadManager extends ApiManager { try { zip.extractEntryTo(entry.entryName, publicDirectory, true, false); createReadStream(pathname).pipe(createWriteStream(targetname)); - Jimp.read(pathname).then((img:any) => { + Jimp.read(pathname).then(img => { DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => { const outputPath = InjectSize(targetname, suffix); if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath)); @@ -278,26 +278,28 @@ export default class UploadManager extends ApiManager { } }); const json = zip.getEntry('docs.json'); - try { - const data = JSON.parse(json.getData().toString('utf8'), retrocycle()); - const { docs, links } = data; - id = getId(data.id); - const rdocs = Object.keys(docs).map(key => docs[key]); - const ldocs = Object.keys(links).map(key => links[key]); - [...rdocs, ...ldocs].forEach(mapFn); - docids = rdocs.map(doc => doc.id); - linkids = ldocs.map(link => link.id); - await Promise.all( - [...rdocs, ...ldocs].map( - doc => - new Promise<void>(res => { - // overwrite mongo doc with json doc contents - Database.Instance.replace(doc.id, doc, (err, r) => res(err && console.log(err)), true); - }) - ) - ); - } catch (e) { - console.log(e); + if (json) { + try { + const data = JSON.parse(json.getData().toString('utf8'), retrocycle()); + const { docs, links } = data; + id = getId(data.id); + const rdocs = Object.keys(docs).map(key => docs[key]); + const ldocs = Object.keys(links).map(key => links[key]); + [...rdocs, ...ldocs].forEach(mapFn); + docids = rdocs.map(doc => doc.id); + linkids = ldocs.map(link => link.id); + await Promise.all( + [...rdocs, ...ldocs].map( + doc => + new Promise<void>(res => { + // overwrite mongo doc with json doc contents + Database.Instance.replace(doc.id, doc, (err, r) => res(err && console.log(err)), true); + }) + ) + ); + } catch (e) { + console.log(e); + } } unlink(path_2.filepath, () => {}); } @@ -346,7 +348,7 @@ export default class UploadManager extends ApiManager { return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => { const ext = path.extname(savedName).toLowerCase(); if (AcceptableMedia.imageFormats.includes(ext)) { - Jimp.read(savedName).then((img:any) => + Jimp.read(savedName).then(img => (!origSuffix ? [{ width: 400, suffix: SizeSuffix.Medium }] : Object.values(DashUploadUtils.Sizes)) // .forEach(({ width, suffix }) => { const outputPath = serverPathToFile(Directory.images, InjectSize(filename, suffix) + ext); diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts index 8d341db63..b6164832f 100644 --- a/src/server/DashStats.ts +++ b/src/server/DashStats.ts @@ -3,23 +3,23 @@ import { Response } from 'express'; import SocketIO from 'socket.io'; import { timeMap } from './ApiManagers/UserManager'; import { WebSocket } from './websocket'; -const fs = require('fs'); +import * as fs from 'fs'; /** * DashStats focuses on tracking user data for each session. - * + * * This includes time connected, number of operations, and * the rate of their operations */ export namespace DashStats { - export const SAMPLING_INTERVAL = 1000; // in milliseconds (ms) - Time interval to update the frontend. + export const SAMPLING_INTERVAL = 1000; // in milliseconds (ms) - Time interval to update the frontend. export const RATE_INTERVAL = 10; // in seconds (s) - Used to calculate rate const statsCSVFilename = './src/server/stats/userLoginStats.csv'; const columns = ['USERNAME', 'ACTION', 'TIME']; /** - * UserStats holds the stats associated with a particular user. + * UserStats holds the stats associated with a particular user. */ interface UserStats { socketId: string; @@ -30,18 +30,18 @@ export namespace DashStats { } /** - * UserLastOperations is the queue object for each user - * storing their past operations. + * UserLastOperations is the queue object for each user + * storing their past operations. */ interface UserLastOperations { sampleOperations: number; // stores how many operations total are in this rate section (10 sec, for example) lastSampleOperations: number; // stores how many total operations were recorded at the last sample - previousOperationsQueue: number[]; // stores the operations to calculate rate. + previousOperationsQueue: number[]; // stores the operations to calculate rate. } /** * StatsDataBundle represents an object that will be sent to the frontend view - * on each websocket update. + * on each websocket update. */ interface StatsDataBundle { connectedUsers: UserStats[]; @@ -57,48 +57,44 @@ export namespace DashStats { } /** - * ServerTraffic describes the current traffic going to the backend. + * ServerTraffic describes the current traffic going to the backend. */ enum ServerTraffic { NOT_BUSY, BUSY, - VERY_BUSY + VERY_BUSY, } // These values can be changed after further testing how many - // users correspond to each traffic level in Dash. + // users correspond to each traffic level in Dash. const BUSY_SERVER_BOUND = 2; const VERY_BUSY_SERVER_BOUND = 3; - const serverTrafficMessages = [ - "Not Busy", - "Busy", - "Very Busy" - ] + const serverTrafficMessages = ['Not Busy', 'Busy', 'Very Busy']; // lastUserOperations maps each username to a UserLastOperations - // structure + // structure export const lastUserOperations = new Map<string, UserLastOperations>(); /** * handleStats is called when the /stats route is called, providing a JSON * object with relevant stats. In this case, we return the number of - * current connections and + * current connections and * @param res Response object from Express */ export function handleStats(res: Response) { let current = getCurrentStats(); const results: CSVStore[] = []; res.json({ - currentConnections: current.length, - socketMap: current, - }); + currentConnections: current.length, + socketMap: current, + }); } /** - * getUpdatedStatesBundle() sends an updated copy of the current stats to the - * frontend /statsview route via websockets. - * + * getUpdatedStatesBundle() sends an updated copy of the current stats to the + * frontend /statsview route via websockets. + * * @returns a StatsDataBundle that is sent to the frontend view on each websocket update */ export function getUpdatedStatsBundle(): StatsDataBundle { @@ -106,43 +102,43 @@ export namespace DashStats { return { connectedUsers: current, - } + }; } /** - * handleStatsView() is called when the /statsview route is called. This + * handleStatsView() is called when the /statsview route is called. This * will use pug to render a frontend view of the current stats - * - * @param res + * + * @param res */ export function handleStatsView(res: Response) { let current = getCurrentStats(); - let connectedUsers = current.map((socketPair) => { - return socketPair.time + " - " + socketPair.username + " Operations: " + socketPair.operations; - }) + let connectedUsers = current.map(socketPair => { + return socketPair.time + ' - ' + socketPair.username + ' Operations: ' + socketPair.operations; + }); let serverTraffic = ServerTraffic.NOT_BUSY; - if(current.length < BUSY_SERVER_BOUND) { + if (current.length < BUSY_SERVER_BOUND) { serverTraffic = ServerTraffic.NOT_BUSY; - } else if(current.length >= BUSY_SERVER_BOUND && current.length < VERY_BUSY_SERVER_BOUND) { + } else if (current.length >= BUSY_SERVER_BOUND && current.length < VERY_BUSY_SERVER_BOUND) { serverTraffic = ServerTraffic.BUSY; } else { serverTraffic = ServerTraffic.VERY_BUSY; } - - res.render("stats.pug", { - title: "Dash Stats", + + res.render('stats.pug', { + title: 'Dash Stats', numConnections: connectedUsers.length, serverTraffic: serverTraffic, - serverTrafficMessage : serverTrafficMessages[serverTraffic], - connectedUsers: connectedUsers + serverTrafficMessage: serverTrafficMessages[serverTraffic], + connectedUsers: connectedUsers, }); } /** - * logUserLogin() writes a login event to the CSV file. - * + * logUserLogin() writes a login event to the CSV file. + * * @param username the username in the format of "username@domain.com logged in" * @param socket the websocket associated with the current connection */ @@ -152,12 +148,12 @@ export namespace DashStats { console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`)); let toWrite: CSVStore = { - USERNAME : username, - ACTION : "loggedIn", - TIME : currentDate.toISOString() - } + USERNAME: username, + ACTION: 'loggedIn', + TIME: currentDate.toISOString(), + }; - let statsFile = fs.createWriteStream(statsCSVFilename, { flags: "a"}); + let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' }); statsFile.write(convertToCSV(toWrite)); statsFile.end(); console.log(cyan(convertToCSV(toWrite))); @@ -165,21 +161,21 @@ export namespace DashStats { } /** - * logUserLogout() writes a logout event to the CSV file. - * + * logUserLogout() writes a logout event to the CSV file. + * * @param username the username in the format of "username@domain.com logged in" - * @param socket the websocket associated with the current connection. + * @param socket the websocket associated with the current connection. */ export function logUserLogout(username: string | undefined, socket: SocketIO.Socket) { if (!(username === undefined)) { let currentDate = new Date(); - let statsFile = fs.createWriteStream(statsCSVFilename, { flags: "a"}); + let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' }); let toWrite: CSVStore = { - USERNAME : username, - ACTION : "loggedOut", - TIME : currentDate.toISOString() - } + USERNAME: username, + ACTION: 'loggedOut', + TIME: currentDate.toISOString(), + }; statsFile.write(convertToCSV(toWrite)); statsFile.end(); } @@ -188,22 +184,22 @@ export namespace DashStats { /** * getLastOperationsOrDefault() is a helper method that will attempt * to query the lastUserOperations map for a specified username. If the - * username is not in the map, an empty UserLastOperations object is returned. - * @param username + * username is not in the map, an empty UserLastOperations object is returned. + * @param username * @returns the user's UserLastOperations structure or an empty * UserLastOperations object (All values set to 0) if the username is not found. */ function getLastOperationsOrDefault(username: string): UserLastOperations { - if(lastUserOperations.get(username) === undefined) { + if (lastUserOperations.get(username) === undefined) { let initializeOperationsQueue = []; - for(let i = 0; i < RATE_INTERVAL; i++) { + for (let i = 0; i < RATE_INTERVAL; i++) { initializeOperationsQueue.push(0); } return { sampleOperations: 0, lastSampleOperations: 0, - previousOperationsQueue: initializeOperationsQueue - } + previousOperationsQueue: initializeOperationsQueue, + }; } return lastUserOperations.get(username)!; } @@ -211,19 +207,19 @@ export namespace DashStats { /** * updateLastOperations updates a specific user's UserLastOperations information * for the current sampling cycle. The method removes old/outdated counts for - * operations from the queue and adds new data for the current sampling - * cycle to the queue, updating the total count as it goes. + * operations from the queue and adds new data for the current sampling + * cycle to the queue, updating the total count as it goes. * @param lastOperationData the old UserLastOperations data that must be updated * @param currentOperations the total number of operations measured for this sampling cycle. - * @returns the udpated UserLastOperations structure. + * @returns the udpated UserLastOperations structure. */ function updateLastOperations(lastOperationData: UserLastOperations, currentOperations: number): UserLastOperations { // create a copy of the UserLastOperations to modify let newLastOperationData: UserLastOperations = { sampleOperations: lastOperationData.sampleOperations, lastSampleOperations: lastOperationData.lastSampleOperations, - previousOperationsQueue: lastOperationData.previousOperationsQueue.slice() - } + previousOperationsQueue: lastOperationData.previousOperationsQueue.slice(), + }; let newSampleOperations = newLastOperationData.sampleOperations; newSampleOperations -= newLastOperationData.previousOperationsQueue.shift()!; // removes and returns the first element of the queue @@ -241,20 +237,20 @@ export namespace DashStats { /** * getUserOperationsOrDefault() is a helper method to get the user's total - * operations for the CURRENT sampling interval. The method will return 0 + * operations for the CURRENT sampling interval. The method will return 0 * if the username is not in the userOperations map. * @param username the username to search the map for - * @returns the total number of operations recorded up to this sampling cycle. + * @returns the total number of operations recorded up to this sampling cycle. */ function getUserOperationsOrDefault(username: string): number { - return WebSocket.userOperations.get(username) === undefined ? 0 : WebSocket.userOperations.get(username)! + return WebSocket.userOperations.get(username) === undefined ? 0 : WebSocket.userOperations.get(username)!; } /** * getCurrentStats() calculates the total stats for this cycle. In this case, - * getCurrentStats() returns an Array of UserStats[] objects describing + * getCurrentStats() returns an Array of UserStats[] objects describing * the stats for each user - * @returns an array of UserStats storing data for each user at the current moment. + * @returns an array of UserStats storing data for each user at the current moment. */ function getCurrentStats(): UserStats[] { let socketPairs: UserStats[] = []; @@ -262,20 +258,20 @@ export namespace DashStats { let username = value.split(' ')[0]; let connectionTime = new Date(timeMap[username]); - let connectionTimeString = connectionTime.toLocaleDateString() + " " + connectionTime.toLocaleTimeString(); + let connectionTimeString = connectionTime.toLocaleDateString() + ' ' + connectionTime.toLocaleTimeString(); if (!key.disconnected) { let lastRecordedOperations = getLastOperationsOrDefault(username); let currentUserOperationCount = getUserOperationsOrDefault(username); - socketPairs.push({ + socketPairs.push({ socketId: key.id, username: username, - time: connectionTimeString.includes("Invalid Date") ? "" : connectionTimeString, - operations : WebSocket.userOperations.get(username) ? WebSocket.userOperations.get(username)! : 0, - rate: lastRecordedOperations.sampleOperations + time: connectionTimeString.includes('Invalid Date') ? '' : connectionTimeString, + operations: WebSocket.userOperations.get(username) ? WebSocket.userOperations.get(username)! : 0, + rate: lastRecordedOperations.sampleOperations, }); - lastUserOperations.set(username, updateLastOperations(lastRecordedOperations,currentUserOperationCount)); + lastUserOperations.set(username, updateLastOperations(lastRecordedOperations, currentUserOperationCount)); } } return socketPairs; @@ -283,9 +279,9 @@ export namespace DashStats { /** * convertToCSV() is a helper method that stringifies a CSVStore object - * that can be written to the CSV file later. + * that can be written to the CSV file later. * @param dataObject the object to stringify - * @returns the object as a string. + * @returns the object as a string. */ function convertToCSV(dataObject: CSVStore): string { return `${dataObject.USERNAME},${dataObject.ACTION},${dataObject.TIME}\n`; diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 0161a8d38..a8e09818e 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -20,12 +20,12 @@ import { AzureManager } from './ApiManagers/AzureManager'; import axios from 'axios'; const spawn = require('child_process').spawn; const { exec } = require('child_process'); -const parse = require('pdf-parse'); -const ffmpeg = require('fluent-ffmpeg'); -const fs = require('fs'); const requestImageSize = require('../client/util/request-image-size'); -const md5File = require('md5-file'); -const autorotate = require('jpeg-autorotate'); +import * as parse from 'pdf-parse'; +import { ffprobe, FfmpegCommand } from 'fluent-ffmpeg'; +import * as fs from 'fs'; +import * as md5File from 'md5-file'; +import * as autorotate from 'jpeg-autorotate'; export enum SizeSuffix { Small = '_s', @@ -95,7 +95,7 @@ export namespace DashUploadUtils { // concatenate the videos await new Promise((resolve, reject) => { - var merge = ffmpeg(); + var merge = new FfmpegCommand(); merge .input(textFilePath) .inputOptions(['-f concat', '-safe 0']) @@ -152,7 +152,7 @@ export namespace DashUploadUtils { exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => { const time = Array.from(stdout.trim().split(':')).reverse(); const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0); - res(resolveExistingFile(name, finalPath, Directory.videos, 'video/mp4', duration, undefined)); + res(resolveExistingFile(name, filepath, Directory.videos, 'video/mp4', duration, undefined)); }); } else { uploadProgress.set(overwriteId, 'starting download'); @@ -185,9 +185,12 @@ export namespace DashUploadUtils { exec(`yt-dlp-o ${filepath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => { const time = Array.from(stdout.trim().split(':')).reverse(); const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0); - const data = { size: 0, filepath, name, mimetype: '', originalFilename: name, newFilename: name, hashAlgorithm: 'md5' as 'md5', type: 'video/mp4' }; - const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), mime: '', hashAlgorithm: 'md5' as 'md5', toJson: () => undefined as any }) }; - res(MoveParsedFile(file, Directory.videos)); + const data = { size: 0, filepath, name, mimetype: 'video', originalFilename: name, newFilename: name, hashAlgorithm: 'md5' as 'md5', type: 'video/mp4' }; + const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), toJson: () => undefined as any }) }; + MoveParsedFile(file, Directory.videos).then(output => { + console.log('OUTPUT = ' + output); + res(output); + }); }); } }); @@ -217,7 +220,7 @@ export namespace DashUploadUtils { if (format.includes('x-matroska')) { console.log('case video'); await new Promise(res => - ffmpeg(file.filepath) + new FfmpegCommand(file.filepath) .videoCodec('copy') // this will copy the data instead of reencode it .save(file.filepath.replace('.mkv', '.mp4')) .on('end', res) @@ -229,7 +232,7 @@ export namespace DashUploadUtils { if (format.includes('quicktime')) { let abort = false; await new Promise<void>(res => - ffmpeg.ffprobe(file.filepath, (err: any, metadata: any) => { + ffprobe(file.filepath, (err: any, metadata: any) => { if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) { abort = true; } @@ -615,13 +618,13 @@ export namespace DashUploadUtils { buffer = buffer2; } catch (e) {} return Jimp.read(buffer ?? fileIn) - .then(async (img:any) => { + .then(async (img: any) => { await Promise.all( sizes.filter(({ width }) => width) .map(({ width, suffix }) => img = img.resize(width, Jimp.AUTO).write(outputPath(suffix)) )); // prettier-ignore return writtenFiles; }) - .catch((e:any) => { + .catch((e: any) => { console.log('ERROR' + e); return writtenFiles; }); diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 12fb42077..8f21966ae 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -2,17 +2,17 @@ declare module 'googlephotos'; declare module 'cors'; +declare module 'image-data-uri'; +declare module 'md5-file'; +declare module 'jpeg-autorotate'; declare module 'webrtc-adapter'; declare module 'bezier-curve'; declare module 'fit-curve'; -declare module 'react-audio-waveform'; declare module 'iink-js'; declare module 'pdfjs-dist/web/pdf_viewer'; +declare module 'react-jsx-parser'; -declare module 'reveal'; -declare module 'react-reveal'; -declare module 'react-reveal/makeCarousel'; declare module 'express-flash'; declare module 'connect-flash' { interface flash {} |