diff options
Diffstat (limited to 'src')
177 files changed, 2400 insertions, 2349 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 4e4414a93..26514622c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,12 +1,12 @@ -import v4 = require('uuid/v4'); -import v5 = require('uuid/v5'); -import { ColorState } from 'react-color'; +import * as uuid from 'uuid'; +import { ColorResult } from 'react-color'; +//import { Socket } from '../node_modules/socket.io-client'; +import { Socket } from '../node_modules/socket.io/dist/index'; import * as rp from 'request-promise'; -import { Socket } from 'socket.io'; import { DocumentType } from './client/documents/DocumentTypes'; import { Colors } from './client/views/global/globalEnums'; import { Message } from './server/Message'; -import Color = require('color'); +import * as Color from 'color'; export namespace Utils { export let CLICK_TIME = 300; @@ -48,11 +48,19 @@ export namespace Utils { } export function GenerateGuid(): string { - return v4(); + return uuid.v4(); } export function GenerateDeterministicGuid(seed: string): string { - return v5(seed, v5.URL); + return uuid.v5(seed, uuid.v5.URL); + } + + export function GenerateMongoId(id: string): string { + return id.length !== 36 ? Utils.GenerateDeterministicGuid(id) : id; + } + + export function GuestID() { + return '__guest__'; } /** @@ -139,7 +147,7 @@ export namespace Utils { return (number < 16 ? '0' : '') + number.toString(16).toUpperCase(); } - export function colorString(color: ColorState) { + export function colorString(color: ColorResult) { return color.hex.startsWith('#') && color.hex.length < 8 ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex; } @@ -398,14 +406,14 @@ export namespace Utils { }; } - export function Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) { + export function Emit<T>(socket: Socket, message: Message<T>, args: T) { log('Emit', message.Name, args, false); socket.emit(message.Message, args); } - export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>; - export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void; - export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> { + export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>; + export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void; + export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> { log('Emit', message.Name, args, false); if (fn) { socket.emit(message.Message, args, loggingCallback('Receiving', fn, message.Name)); @@ -414,7 +422,7 @@ export namespace Utils { } } - export function AddServerHandler<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, handler: (args: T) => any) { + export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) { socket.on(message.Message, loggingCallback('Incoming', handler, message.Name)); } @@ -425,10 +433,10 @@ export namespace Utils { }); } export type RoomHandler = (socket: Socket, room: string) => any; - export type UsedSockets = Socket | SocketIOClient.Socket; + export type UsedSockets = Socket; export type RoomMessage = 'create or join' | 'created' | 'joined'; export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) { - socket.on(message, room => handler(socket, room)); + socket.on(message, (room: any) => handler(socket, room)); } } diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 353e11775..df92fe50e 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -7,6 +7,7 @@ import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols'; import { ObjectField } from '../fields/ObjectField'; import { RefField } from '../fields/RefField'; import { DocCast, StrCast } from '../fields/Types'; +import { Socket } from '../../node_modules/socket.io/dist/index'; //import MobileInkOverlay from '../mobile/MobileInkOverlay'; import { emptyFunction, Utils } from '../Utils'; import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message'; @@ -76,7 +77,7 @@ export namespace DocServer { json: true, }); } - export let _socket: SocketIOClient.Socket; + export let _socket: Socket; // this client's distinct GUID created at initialization let USER_ID: string; // indicates whether or not a document is currently being udpated, and, if so, its id @@ -189,20 +190,20 @@ export namespace DocServer { Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete); Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser); - // mobile ink overlay socket events to communicate between mobile view and desktop view - _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => { - // MobileInkOverlay.Instance.drawStroke(content); - }); - _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => { - //GestureOverlay.Instance.enableMobileInkOverlay(content); - // MobileInkOverlay.Instance.initMobileInkOverlay(content); - }); - _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => { - // MobileInkOverlay.Instance.updatePosition(content); - }); - _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => { - // MobileInkOverlay.Instance.uploadDocument(content); - }); + // // mobile ink overlay socket events to communicate between mobile view and desktop view + // _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => { + // // MobileInkOverlay.Instance.drawStroke(content); + // }); + // _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => { + // //GestureOverlay.Instance.enableMobileInkOverlay(content); + // // MobileInkOverlay.Instance.initMobileInkOverlay(content); + // }); + // _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => { + // // MobileInkOverlay.Instance.updatePosition(content); + // }); + // _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => { + // // MobileInkOverlay.Instance.uploadDocument(content); + // }); } function errorFunc(): never { @@ -470,10 +471,13 @@ export namespace DocServer { // to their actual RefField | undefined values. This return value either becomes the input // argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...)) // or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])). - return ids.reduce((map, id) => { - map[id] = _cache[id] as any; - return map; - }, {} as { [id: string]: Opt<RefField> }); + return ids.reduce( + (map, id) => { + map[id] = _cache[id] as any; + return map; + }, + {} as { [id: string]: Opt<RefField> } + ); }; let _GetRefFields: (ids: string[]) => Promise<{ [id: string]: Opt<RefField> }> = errorFunc; diff --git a/src/client/Network.ts b/src/client/Network.ts index 89b31fdca..cdcd5225a 100644 --- a/src/client/Network.ts +++ b/src/client/Network.ts @@ -1,5 +1,5 @@ import { Utils } from '../Utils'; -import requestPromise = require('request-promise'); +import * as requestPromise from 'request-promise'; import { Upload } from '../server/SharedMediaTypes'; /** diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 551dca073..c8f381cc0 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -84,7 +84,7 @@ export namespace GoogleApiClientUtils { }; try { const schema: docs_v1.Schema$Document = await Networking.PostToServer(path, parameters); - return schema.documentId; + return schema.documentId === null ? undefined : schema.documentId; } catch { return undefined; } @@ -195,7 +195,7 @@ export namespace GoogleApiClientUtils { const title = document.title; let bodyLines = Utils.extractText(document).text.split("\n"); options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length)); - return { title, bodyLines }; + return { title: title ?? "", bodyLines }; } }); }; diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 6bde7989b..85634bc8b 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,4 +1,4 @@ -import { Configuration, OpenAIApi } from 'openai'; +import { ClientOptions, OpenAI } from 'openai'; enum GPTCallType { SUMMARY = 'summary', @@ -29,17 +29,17 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { if (callType === GPTCallType.SUMMARY) inputText += '.'; const opts: GPTCallOpts = callTypeMap[callType]; try { - const configuration = new Configuration({ + const configuration:ClientOptions ={ apiKey: process.env.OPENAI_KEY, - }); - const openai = new OpenAIApi(configuration); - const response = await openai.createCompletion({ + }; + const openai = new OpenAI(configuration); + const response = await openai.completions.create({ model: opts.model, max_tokens: opts.maxTokens, temperature: opts.temp, prompt: `${opts.prompt}${inputText}`, }); - return response.data.choices[0].text; + return response.choices[0].text; } catch (err) { console.log(err); return 'Error connecting with API.'; @@ -48,16 +48,16 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { const gptImageCall = async (prompt: string, n?: number) => { try { - const configuration = new Configuration({ + const configuration:ClientOptions = { apiKey: process.env.OPENAI_KEY, - }); - const openai = new OpenAIApi(configuration); - const response = await openai.createImage({ + }; + const openai = new OpenAI(configuration); + const response = await openai.images.generate({ prompt: prompt, n: n ?? 1, size: '1024x1024', }); - return response.data.data.map(data => data.url); + return response.data.map((data:any) => data.url); // return response.data.data[0].url; } catch (err) { console.error(err); diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 2da9927c0..ceb80acbc 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -10,7 +10,7 @@ import { DocumentView } from '../../views/nodes/DocumentView'; import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; import '../../views/nodes/WebBox.scss'; import './YoutubeBox.scss'; -import React = require('react'); +import * as React from 'react'; interface VideoTemplate { thumbnailUrl: string; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 929660ff4..4ec100728 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -30,7 +30,6 @@ import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMu import { CollectionView } from '../views/collections/CollectionView'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; -import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; import { AudioBox, media_state } from '../views/nodes/AudioBox'; import { ColorBox } from '../views/nodes/ColorBox'; @@ -61,6 +60,8 @@ import { VideoBox } from '../views/nodes/VideoBox'; import { WebBox } from '../views/nodes/WebBox'; import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; +//import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss'; +const DFLT_IMAGE_NATIVE_DIM = '900px'; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); class EmptyBox { @@ -1299,7 +1300,7 @@ export namespace DocUtils { // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); const matchLink = (value: string, anchor: Doc) => { - const linkedToExp = value?.split('='); + const linkedToExp = (value ?? '').split('='); if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value; return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; }; @@ -1909,7 +1910,7 @@ export namespace DocUtils { const generatedDocuments: Doc[] = []; Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => { const { - source: { name, type }, + source: { newFilename, originalFilename, mimetype }, result, } = upfiles.lastElement(); if ((result as any).message) { @@ -1918,7 +1919,7 @@ export namespace DocUtils { overwriteDoc.loadingError = (result as any).message; LoadingBox.removeCurrentlyLoading(overwriteDoc); } - } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc); }); } @@ -1938,10 +1939,10 @@ export namespace DocUtils { const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs); for (const { - source: { name, type }, + source: { newFilename, mimetype }, result, } of upfiles) { - name && type && processFileupload(generatedDocuments, name, type, result, options); + newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options); } return generatedDocuments; } @@ -1951,15 +1952,15 @@ export namespace DocUtils { // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid. Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => { const { - source: { name, type }, + source: { newFilename, mimetype }, result, - } = upfiles.lastElement() ?? { source: { name: '', type: '' }, result: { message: 'upload failed' } }; + } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } }; if ((result as any).message) { if (overwriteDoc) { overwriteDoc.loadingError = (result as any).message; LoadingBox.removeCurrentlyLoading(overwriteDoc); } - } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc); }); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d3ed4ca7c..8b5f23aa4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -561,7 +561,7 @@ export class CurrentUserUtils { toolTip: "Empty recently closed",}; DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")}); - if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) { + if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) { recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!]) } return recentlyClosed; @@ -946,7 +946,7 @@ export class CurrentUserUtils { runInAction(() => CurrentUserUtils.ServerVersion = result.version); Doc.CurrentUserEmail = result.email; resolvedPorts = result.resolvedPorts as any; - DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email); + DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email); if (result.cacheDocumentIds) { const ids = result.cacheDocumentIds.split(";"); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 162a0a11f..427b1c85f 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -7,13 +7,14 @@ import { ScriptField } from '../../fields/ScriptField'; import { ScriptCast, StrCast } from '../../fields/Types'; import { emptyFunction, Utils } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; -import * as globalCssVariables from '../views/global/globalCssVariables.scss'; import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../views/nodes/DocumentView'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; +// import * as globalCssVariables from '../views/global/globalCssVariables.scss'; +const contextMenuZindex = 100002; export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove @@ -85,7 +86,16 @@ export namespace DragManager { // event called when the drag operation results in a drop action export class DropEvent { - constructor(readonly x: number, readonly y: number, readonly complete: DragCompleteEvent, readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, readonly ctrlKey: boolean, readonly embedKey: boolean) {} + constructor( + readonly x: number, + readonly y: number, + readonly complete: DragCompleteEvent, + readonly shiftKey: boolean, + readonly altKey: boolean, + readonly metaKey: boolean, + readonly ctrlKey: boolean, + readonly embedKey: boolean + ) {} } // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated @@ -209,16 +219,14 @@ export namespace DragManager { !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : docDragData.dropAction === 'embed' - ? Doc.BestEmbedding(d) - : docDragData.dropAction === 'add' - ? d - : docDragData.dropAction === 'proto' - ? Doc.GetProto(d) - : docDragData.dropAction === 'copy' - ? ( - await Doc.MakeClone(d) - ).clone - : d + ? Doc.BestEmbedding(d) + : docDragData.dropAction === 'add' + ? d + : docDragData.dropAction === 'proto' + ? Doc.GetProto(d) + : docDragData.dropAction === 'copy' + ? (await Doc.MakeClone(d)).clone + : d ) ) ).filter(d => d); @@ -428,7 +436,7 @@ export namespace DragManager { color: 'black', transition: 'none', borderRadius: getComputedStyle(ele).borderRadius, - zIndex: globalCssVariables.contextMenuZindex, + zIndex: contextMenuZindex, transformOrigin: '0 0', width, height, diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 8973306bf..c8c93b7d0 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -52,7 +52,7 @@ export class GroupManager extends React.Component<{}> { * Fetches the list of users stored on the database. */ populateUsers = async () => { - if (Doc.UserDoc()[Id] !== '__guest__') { + if (Doc.UserDoc()[Id] !== Utils.GuestID()) { const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); @@ -258,10 +258,7 @@ export class GroupManager extends React.Component<{}> { alert('Please select a unique group name'); return; } - this.createGroupDoc( - value, - this.selectedUsers?.map(user => user.value) - ); + this.createGroupDoc(value, this.selectedUsers?.map(user => user.value)); this.selectedUsers = null; this.inputRef.current!.value = ''; this.buttonColour = '#979797'; diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 18aee6444..2f1a336cc 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -85,7 +85,7 @@ export namespace HistoryUtil { }; 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 | undefined) { + function parse(parser: ParserValue, value: string | (string | null)[] | null | undefined) { if (value === undefined || value === null) { return value; } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index c45143f43..30e448797 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -20,8 +20,8 @@ import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; import { DocumentManager } from '../DocumentManager'; import './DirectoryImportBox.scss'; import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; -import React = require('react'); -import _ = require('lodash'); +import * as React from 'react'; +import * as _ from 'lodash'; const unsupported = ['text/html', 'text/plain']; @@ -119,7 +119,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { await Promise.all( uploads.map(async response => { const { - source: { type }, + source: { mimetype }, result, } = response; if (result instanceof Error) { @@ -127,7 +127,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { } const { accessPaths, exifData } = result; const path = Utils.prepend(accessPaths.agnostic.client); - const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 })); + const document = mimetype && (await DocUtils.DocumentFromType(mimetype, path, { _width: 300 })); const { data, error } = exifData; if (document) { Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx index 45d8c0c63..58a09b9c9 100644 --- a/src/client/util/Import & Export/ImportMetadataEntry.tsx +++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx @@ -1,10 +1,10 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import { EditableView } from "../../views/EditableView"; -import { action, computed } from "mobx"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Doc } from "../../../fields/Doc"; -import { StrCast, BoolCast } from "../../../fields/Types"; +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { EditableView } from '../../views/EditableView'; +import { action, computed } from 'mobx'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Doc } from '../../../fields/Doc'; +import { StrCast, BoolCast } from '../../../fields/Types'; interface KeyValueProps { Document: Doc; @@ -12,19 +12,18 @@ interface KeyValueProps { next: () => void; } -export const keyPlaceholder = "Key"; -export const valuePlaceholder = "Value"; +export const keyPlaceholder = 'Key'; +export const valuePlaceholder = 'Value'; @observer export default class ImportMetadataEntry extends React.Component<KeyValueProps> { - private keyRef = React.createRef<EditableView>(); private valueRef = React.createRef<EditableView>(); private checkRef = React.createRef<HTMLInputElement>(); @computed public get valid() { - return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder); + return this.key.length > 0 && this.key !== keyPlaceholder && this.value.length > 0 && this.value !== valuePlaceholder; } @computed @@ -66,7 +65,7 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps> this.valueRef.current && this.valueRef.current.setIsFocused(true); this.key.length === 0 && (this.key = keyPlaceholder); return true; - } + }; @action updateValue = (newValue: string, shiftDown: boolean) => { @@ -75,68 +74,45 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps> this.value.length > 0 && shiftDown && this.props.next(); this.value.length === 0 && (this.value = valuePlaceholder); return true; - } + }; render() { const keyValueStyle: React.CSSProperties = { paddingLeft: 10, - width: "50%", + width: '50%', opacity: this.valid ? 1 : 0.5, }; return ( <div style={{ - display: "flex", - flexDirection: "row", + display: 'flex', + flexDirection: 'row', paddingBottom: 5, paddingRight: 5, - justifyContent: "center", - alignItems: "center", - alignContent: "center" - }} - > - <input - onChange={e => this.onDataDoc = e.target.checked} - ref={this.checkRef} - style={{ margin: "0 10px 0 15px" }} - type="checkbox" - title={"Add to Data Document?"} - checked={this.onDataDoc} - /> - <div className={"key_container"} style={keyValueStyle}> - <EditableView - ref={this.keyRef} - contents={this.key} - SetValue={this.updateKey} - GetValue={() => ""} - oneLine={true} - /> + justifyContent: 'center', + alignItems: 'center', + alignContent: 'center', + }}> + <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} /> + <div className={'key_container'} style={keyValueStyle}> + <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} /> </div> - <div - className={"value_container"} - style={keyValueStyle}> - <EditableView - ref={this.valueRef} - contents={this.value} - SetValue={this.updateValue} - GetValue={() => ""} - oneLine={true} - /> + <div className={'value_container'} style={keyValueStyle}> + <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} /> </div> - <div onClick={() => this.props.remove(this)} title={"Delete Entry"}> + <div onClick={() => this.props.remove(this)} title={'Delete Entry'}> <FontAwesomeIcon - icon={"plus"} - color={"red"} - size={"1x"} + icon={'plus'} + color={'red'} + size={'1x'} style={{ marginLeft: 15, marginRight: 15, - transform: "rotate(45deg)" + transform: 'rotate(45deg)', }} /> </div> </div> ); } - -}
\ No newline at end of file +} diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index be885312d..a2f5826fe 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { GestureUtils } from '../../pen-gestures/GestureUtils'; import { Utils } from '../../Utils'; import './InteractionUtils.scss'; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 400b63a1c..f0a5f9ed9 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -120,6 +120,7 @@ class ScriptingCompilerHost { } return undefined; } + // getDefaultLibFileName(options: ts.CompilerOptions): string { getDefaultLibFileName(options: any): string { return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means... @@ -247,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile('file.ts', funcScript); - if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); + if (typecheck && false) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); const program = ts.createProgram(['file.ts'], {}, host); const testResult = program.emit(); const outputText = host.readFile('file.js'); diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx index d4620ae3f..785526ab3 100644 --- a/src/client/util/ScrollBox.tsx +++ b/src/client/util/ScrollBox.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> { onWheel = (e: React.WheelEvent) => { diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 76037a7e9..8daa69890 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -1,6 +1,5 @@ import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr'; import { Field } from '../../fields/Doc'; -import { ClientUtils } from './ClientUtils'; let serializing = 0; export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) { diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 34e294a4a..403f4e090 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -133,7 +133,7 @@ export class SharingManager extends React.Component<{}> { * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument. */ populateUsers = async () => { - if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') { + if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) { this.populating = true; const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail); diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index b25d51b41..738902a31 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; -import v4 = require('uuid/v4'); +import * as uuid from 'uuid'; import '.././SettingsManager.scss'; import './ReportManager.scss'; -import Dropzone from 'react-dropzone'; import ReactLoading from 'react-loading'; import { action, observable } from 'mobx'; import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs'; import { CgClose } from 'react-icons/cg'; -import { AiOutlineUpload } from 'react-icons/ai'; import { HiOutlineArrowLeft } from 'react-icons/hi'; import { Issue } from './reportManagerSchema'; import { observer } from 'mobx-react'; @@ -156,7 +154,7 @@ export class ReportManager extends React.Component<{}> { * @param files uploaded files */ private onDrop = (files: File[]) => { - this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: v4(), file }))] }); + this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: uuid.v4(), file }))] }); }; /** @@ -338,7 +336,7 @@ export class ReportManager extends React.Component<{}> { multiple onChange={e => { if (!e.target.files) return; - this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: v4(), file }))] }); + this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: uuid.v4(), file }))] }); }} /> {this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>} diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx index e870c073d..1e226bf6d 100644 --- a/src/client/util/reportManager/ReportManagerComponents.tsx +++ b/src/client/util/reportManager/ReportManagerComponents.tsx @@ -289,7 +289,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { </div> </div> )} - <ReactMarkdown children={issueBody} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} /> + <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} /> </div> ); }; diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index 16e76694d..412b8ba6e 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { observable, action, runInAction } from 'mobx'; import './AntimodeMenu.scss'; import { StrCast } from '../../fields/Types'; diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index c779ce8c4..1fd2fedc3 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import axios from 'axios'; import { action, computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -102,7 +102,15 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { render() { return ( <div className="audioWaveform"> - <Waveform color={Colors.MEDIUM_BLUE_ALT} height={this.waveHeight} barWidth={200 / this.audioBuckets.length} pos={this.props.duration} duration={this.props.duration} peaks={Array.from(this.audioBuckets)} progressColor={Colors.MEDIUM_BLUE_ALT} /> + <Waveform + color={Colors.MEDIUM_BLUE_ALT} + height={this.waveHeight} + barWidth={200 / this.audioBuckets.length} + pos={this.props.duration} + duration={this.props.duration} + peaks={Array.from(this.audioBuckets)} + progressColor={Colors.MEDIUM_BLUE_ALT} + /> </div> ); } diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx index 66d1bd63d..0a34b283b 100644 --- a/src/client/views/ComponentDecorations.tsx +++ b/src/client/views/ComponentDecorations.tsx @@ -1,14 +1,14 @@ -import { observer } from "mobx-react"; -import { SelectionManager } from "../util/SelectionManager"; +import { observer } from 'mobx-react'; +import { SelectionManager } from '../util/SelectionManager'; import './ComponentDecorations.scss'; -import React = require("react"); +import * as React from 'react'; @observer -export class ComponentDecorations extends React.Component<{ boundsTop: number, boundsLeft: number }, { value: string }> { +export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> { static Instance: ComponentDecorations; render() { const seldoc = SelectionManager.Views().lastElement(); - return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? (null); + return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null; } -}
\ No newline at end of file +} diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index adefc7e9c..57bc11385 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index c2cbca3e1..6d97d965e 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { observable, action, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 59d7e75da..a64722a0b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,6 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; @@ -27,10 +27,10 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { PinProps } from './nodes/trails'; import { TemplateMenu } from './TemplateMenu'; -import React = require('react'); -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; +import * as React from 'react'; +// import * as higflyout from '@hig/flyout'; +// export const { anchorPoints } = higflyout; +// export const Flyout = higflyout.default; const cloud: IconProp = 'cloud-upload-alt'; const fetch: IconProp = 'sync-alt'; @@ -423,7 +423,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !view0 ? null : ( <Tooltip title={<div className="dash-tooltip">Show metadata panel</div>}> <div className="documentButtonBar-linkFlyout"> - <Flyout + {/* <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={ <MetadataEntryMenu @@ -432,12 +432,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .filter(dv => dv) .map(dv => dv!.props.Document)} suggestWithFunction - /> /* tfs: @bcz This might need to be the data document? */ + /> // tfs: @bcz This might need to be the data document? }> <div className={'documentButtonBar-linkButton-' + 'empty'} onPointerDown={e => e.stopPropagation()}> {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />} </div> - </Flyout> + </Flyout> */} </div> </Tooltip> ); @@ -501,7 +501,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !view0 ? null : ( <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom"> <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}> - <Flyout + {/* <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => (this._embedDown = true))} onClose={action(() => (this._embedDown = false))} @@ -516,7 +516,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}> <FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" /> </div> - </Flyout> + </Flyout> */} </div> </Tooltip> ); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b9fcf5360..b4c19df2d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { IconButton } from 'browndash-components'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -33,8 +33,8 @@ import { LightboxView } from './LightboxView'; import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; -import React = require('react'); -import _ = require('lodash'); +import * as React from 'react'; +import * as _ from 'lodash'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index abb7ed7ee..6bc0e5424 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as Autosuggest from 'react-autosuggest'; @@ -222,12 +222,14 @@ export class EditableView extends React.Component<EditableProps> { className: 'editableView-input', onKeyDown: this.onKeyDown, autoFocus: true, + // @ts-ignore onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true, false), onPointerDown: this.stopPropagation, onClick: this.stopPropagation, onPointerUp: this.stopPropagation, onKeyPress: this.stopPropagation, value: this.props.autosuggestProps.value, + // @ts-ignore onChange: this.props.autosuggestProps.onChange, }} /> diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 1deca401d..a3c4350d9 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, computed, observable, ObservableMap } from 'mobx'; import { observer } from 'mobx-react'; import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider'; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 559f245d9..214da5541 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import * as fitCurve from 'fit-curve'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -14,7 +14,6 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import './GestureOverlay.scss'; -import { InkTranscription } from './InkTranscription'; import { ActiveArrowEnd, ActiveArrowScale, @@ -178,7 +177,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest newPoints.pop(); const controlPoints: { X: number; Y: number }[] = []; - const bezierCurves = fitCurve(newPoints, 10); + const bezierCurves = (fitCurve as any)(newPoints, 10); for (const curve of bezierCurves) { controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index e5141b7f4..b835a3798 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 65f6a6dfa..a00c26a07 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 17136a737..b2ac208ca 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -1,350 +1,350 @@ -import * as iink from 'iink-js'; -import { action, observable } from 'mobx'; -import * as React from 'react'; -import { Doc, DocListCast } from '../../fields/Doc'; -import { InkData, InkField, InkTool } from '../../fields/InkField'; -import { Cast, DateCast, NumCast } from '../../fields/Types'; -import { aggregateBounds } from '../../Utils'; -import { DocumentType } from '../documents/DocumentTypes'; -import { DocumentManager } from '../util/DocumentManager'; -import { CollectionFreeFormView } from './collections/collectionFreeForm'; -import { InkingStroke } from './InkingStroke'; -import './InkTranscription.scss'; - -/** - * Class component that handles inking in writing mode - */ -export class InkTranscription extends React.Component { - static Instance: InkTranscription; - - @observable _mathRegister: any; - @observable _mathRef: any; - @observable _textRegister: any; - @observable _textRef: any; - private lastJiix: any; - private currGroup?: Doc; - - constructor(props: Readonly<{}>) { - super(props); - - InkTranscription.Instance = this; - } - - componentWillUnmount() { - this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); - this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); - } - - @action - setMathRef = (r: any) => { - if (!this._mathRegister) { - this._mathRegister = r - ? iink.register(r, { - recognitionParams: { - type: 'MATH', - protocol: 'WEBSOCKET', - server: { - host: 'cloud.myscript.com', - applicationKey: process.env.IINKJS_APP, - hmacKey: process.env.IINKJS_HMAC, - websocket: { - pingEnabled: false, - autoReconnect: true, - }, - }, - iink: { - math: { - mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'], - }, - export: { - jiix: { - strokes: true, - }, - }, - }, - }, - }) - : null; - } - - r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); - - return (this._mathRef = r); - }; - - @action - setTextRef = (r: any) => { - if (!this._textRegister) { - this._textRegister = r - ? iink.register(r, { - recognitionParams: { - type: 'TEXT', - protocol: 'WEBSOCKET', - server: { - host: 'cloud.myscript.com', - applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', - hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', - websocket: { - pingEnabled: false, - autoReconnect: true, - }, - }, - iink: { - text: { - mimeTypes: ['text/plain'], - }, - export: { - jiix: { - strokes: true, - }, - }, - }, - }, - }) - : null; - } - - r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); - - return (this._textRef = r); - }; - - /** - * Handles processing Dash Doc data for ink transcription. - * - * @param groupDoc the group which contains the ink strokes we want to transcribe - * @param inkDocs the ink docs contained within the selected group - * @param math boolean whether to do math transcription or not - */ - transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => { - if (!groupDoc) return; - const validInks = inkDocs.filter(s => s.type === DocumentType.INK); - - const strokes: InkData[] = []; - const times: number[] = []; - validInks - .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField)) - .forEach(i => { - const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null); - const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke; - strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y }))); - times.push(DateCast(i.author_date).getDate().getTime()); - }); - - this.currGroup = groupDoc; - - const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) }; - const processGestures = false; - - if (math) { - this._mathRef.editor.pointerEvents(pointerData, processGestures); - } else { - this._textRef.editor.pointerEvents(pointerData, processGestures); - } - }; - - /** - * Converts the Dash Ink Data to JSON. - * - * @param stroke The dash ink data - * @param time the time of the stroke - * @returns json object representation of ink data - */ - inkJSON = (stroke: InkData, time: number) => { - return { - pointerType: 'PEN', - pointerId: 1, - x: stroke.map(point => point.X), - y: stroke.map(point => point.Y), - t: new Array(stroke.length).fill(time), - p: new Array(stroke.length).fill(1.0), - }; - }; - - /** - * Creates subgroups for each word for the whole text transcription - * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs) - */ - subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => { - // iterate through the keys of wordInkDocMap - wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => { - const selected = inkDocs.slice(); - if (!selected) { - return; - } - const ctx = await Cast(selected[0].embedContainer, Doc); - if (!ctx) { - return; - } - const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView; - - if (!docView) return; - const marqViewRef = docView._marqueeViewRef.current; - if (!marqViewRef) return; - this.groupInkDocs(selected, docView, word); - }); - }; - - /** - * Event listener function for when the 'exported' event is heard. - * - * @param e the event objects - * @param ref the ref to the editor - */ - exportInk = (e: any, ref: any) => { - const exports = e.detail.exports; - if (exports) { - if (exports['application/x-latex']) { - const latex = exports['application/x-latex']; - if (this.currGroup) { - this.currGroup.text = latex; - this.currGroup.title = latex; - } - - ref.editor.clear(); - } else if (exports['text/plain']) { - if (exports['application/vnd.myscript.jiix']) { - this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']); - // map timestamp to strokes - const timestampWord = new Map<number, string>(); - this.lastJiix.words.map((word: any) => { - if (word.items) { - word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => { - const ms = Date.parse(i.timestamp); - timestampWord.set(ms, word.label); - }); - } - }); - - const wordInkDocMap = new Map<string, Doc[]>(); - if (this.currGroup) { - const docList = DocListCast(this.currGroup.data); - docList.forEach((inkDoc: Doc) => { - // just having the times match up and be a unique value (actual timestamp doesn't matter) - const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000; - const word = timestampWord.get(ms); - if (!word) { - return; - } - const entry = wordInkDocMap.get(word); - if (entry) { - entry.push(inkDoc); - wordInkDocMap.set(word, entry); - } else { - const newEntry = [inkDoc]; - wordInkDocMap.set(word, newEntry); - } - }); - if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap); - } - } - const text = exports['text/plain']; - - if (this.currGroup) { - this.currGroup.transcription = text; - this.currGroup.title = text.split('\n')[0]; - } - - ref.editor.clear(); - } - } - }; - - /** - * Creates the ink grouping once the user leaves the writing mode. - */ - createInkGroup() { - // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - const selected = ffView.unprocessedDocs; - const newCollection = this.groupInkDocs( - selected.filter(doc => doc.embedContainer), - ffView - ); - ffView.unprocessedDocs = []; - - InkTranscription.Instance.transcribeInk(newCollection, selected, false); - }); - } - CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); - } - - /** - * Creates the groupings for a given list of ink docs on a specific doc view - * @param selected: the list of ink docs to create a grouping of - * @param docView: the view in which we want the grouping to be created - * @param word: optional param if the group we are creating is a word (subgrouping individual words) - * @returns a new collection Doc or undefined if the grouping fails - */ - groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined { - const bounds: { x: number; y: number; width?: number; height?: number }[] = []; - - // calculate the necessary bounds from the selected ink docs - selected.map( - action(d => { - const x = NumCast(d.x); - const y = NumCast(d.y); - const width = NumCast(d._width); - const height = NumCast(d._height); - bounds.push({ x, y, width, height }); - }) - ); - - // calculate the aggregated bounds - const aggregBounds = aggregateBounds(bounds, 0, 0); - const marqViewRef = docView._marqueeViewRef.current; - - // set the vals for bounds in marqueeView - if (marqViewRef) { - marqViewRef._downX = aggregBounds.x; - marqViewRef._downY = aggregBounds.y; - marqViewRef._lastX = aggregBounds.r; - marqViewRef._lastY = aggregBounds.b; - } - - // map through all the selected ink strokes and create the groupings - selected.map( - action(d => { - const dx = NumCast(d.x); - const dy = NumCast(d.y); - delete d.x; - delete d.y; - delete d.activeFrame; - delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // calculate pos based on bounds - if (marqViewRef?.Bounds) { - d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; - d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; - } - return d; - }) - ); - docView.props.removeDocument?.(selected); - // Gets a collection based on the selected nodes using a marquee view ref - const newCollection = marqViewRef?.getCollection(selected, undefined, true); - if (newCollection) { - newCollection.width = NumCast(newCollection._width); - newCollection.height = NumCast(newCollection._height); - // if the grouping we are creating is an individual word - if (word) { - newCollection.title = word; - } - } - - // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - newCollection && docView.props.addDocument?.(newCollection); - return newCollection; - } - - render() { - return ( - <div className="ink-transcription"> - <div className="math-editor" ref={this.setMathRef} touch-action="none"></div> - <div className="text-editor" ref={this.setTextRef} touch-action="none"></div> - </div> - ); - } -} +// import * as iink from 'iink-js'; +// import { action, observable } from 'mobx'; +// import * as React from 'react'; +// import { Doc, DocListCast } from '../../fields/Doc'; +// import { InkData, InkField, InkTool } from '../../fields/InkField'; +// import { Cast, DateCast, NumCast } from '../../fields/Types'; +// import { aggregateBounds } from '../../Utils'; +// import { DocumentType } from '../documents/DocumentTypes'; +// import { DocumentManager } from '../util/DocumentManager'; +// import { CollectionFreeFormView } from './collections/collectionFreeForm'; +// import { InkingStroke } from './InkingStroke'; +// import './InkTranscription.scss'; + +// /** +// * Class component that handles inking in writing mode +// */ +// export class InkTranscription extends React.Component { +// static Instance: InkTranscription; + +// @observable _mathRegister: any; +// @observable _mathRef: any; +// @observable _textRegister: any; +// @observable _textRef: any; +// private lastJiix: any; +// private currGroup?: Doc; + +// constructor(props: Readonly<{}>) { +// super(props); + +// InkTranscription.Instance = this; +// } + +// componentWillUnmount() { +// this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); +// this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); +// } + +// @action +// setMathRef = (r: any) => { +// if (!this._mathRegister) { +// this._mathRegister = r +// ? iink.register(r, { +// recognitionParams: { +// type: 'MATH', +// protocol: 'WEBSOCKET', +// server: { +// host: 'cloud.myscript.com', +// applicationKey: process.env.IINKJS_APP, +// hmacKey: process.env.IINKJS_HMAC, +// websocket: { +// pingEnabled: false, +// autoReconnect: true, +// }, +// }, +// iink: { +// math: { +// mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'], +// }, +// export: { +// jiix: { +// strokes: true, +// }, +// }, +// }, +// }, +// }) +// : null; +// } + +// r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); + +// return (this._mathRef = r); +// }; + +// @action +// setTextRef = (r: any) => { +// if (!this._textRegister) { +// this._textRegister = r +// ? iink.register(r, { +// recognitionParams: { +// type: 'TEXT', +// protocol: 'WEBSOCKET', +// server: { +// host: 'cloud.myscript.com', +// applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', +// hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', +// websocket: { +// pingEnabled: false, +// autoReconnect: true, +// }, +// }, +// iink: { +// text: { +// mimeTypes: ['text/plain'], +// }, +// export: { +// jiix: { +// strokes: true, +// }, +// }, +// }, +// }, +// }) +// : null; +// } + +// r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + +// return (this._textRef = r); +// }; + +// /** +// * Handles processing Dash Doc data for ink transcription. +// * +// * @param groupDoc the group which contains the ink strokes we want to transcribe +// * @param inkDocs the ink docs contained within the selected group +// * @param math boolean whether to do math transcription or not +// */ +// transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => { +// if (!groupDoc) return; +// const validInks = inkDocs.filter(s => s.type === DocumentType.INK); + +// const strokes: InkData[] = []; +// const times: number[] = []; +// validInks +// .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField)) +// .forEach(i => { +// const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null); +// const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke; +// strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y }))); +// times.push(DateCast(i.author_date).getDate().getTime()); +// }); + +// this.currGroup = groupDoc; + +// const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) }; +// const processGestures = false; + +// if (math) { +// this._mathRef.editor.pointerEvents(pointerData, processGestures); +// } else { +// this._textRef.editor.pointerEvents(pointerData, processGestures); +// } +// }; + +// /** +// * Converts the Dash Ink Data to JSON. +// * +// * @param stroke The dash ink data +// * @param time the time of the stroke +// * @returns json object representation of ink data +// */ +// inkJSON = (stroke: InkData, time: number) => { +// return { +// pointerType: 'PEN', +// pointerId: 1, +// x: stroke.map(point => point.X), +// y: stroke.map(point => point.Y), +// t: new Array(stroke.length).fill(time), +// p: new Array(stroke.length).fill(1.0), +// }; +// }; + +// /** +// * Creates subgroups for each word for the whole text transcription +// * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs) +// */ +// subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => { +// // iterate through the keys of wordInkDocMap +// wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => { +// const selected = inkDocs.slice(); +// if (!selected) { +// return; +// } +// const ctx = await Cast(selected[0].embedContainer, Doc); +// if (!ctx) { +// return; +// } +// const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView; + +// if (!docView) return; +// const marqViewRef = docView._marqueeViewRef.current; +// if (!marqViewRef) return; +// this.groupInkDocs(selected, docView, word); +// }); +// }; + +// /** +// * Event listener function for when the 'exported' event is heard. +// * +// * @param e the event objects +// * @param ref the ref to the editor +// */ +// exportInk = (e: any, ref: any) => { +// const exports = e.detail.exports; +// if (exports) { +// if (exports['application/x-latex']) { +// const latex = exports['application/x-latex']; +// if (this.currGroup) { +// this.currGroup.text = latex; +// this.currGroup.title = latex; +// } + +// ref.editor.clear(); +// } else if (exports['text/plain']) { +// if (exports['application/vnd.myscript.jiix']) { +// this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']); +// // map timestamp to strokes +// const timestampWord = new Map<number, string>(); +// this.lastJiix.words.map((word: any) => { +// if (word.items) { +// word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => { +// const ms = Date.parse(i.timestamp); +// timestampWord.set(ms, word.label); +// }); +// } +// }); + +// const wordInkDocMap = new Map<string, Doc[]>(); +// if (this.currGroup) { +// const docList = DocListCast(this.currGroup.data); +// docList.forEach((inkDoc: Doc) => { +// // just having the times match up and be a unique value (actual timestamp doesn't matter) +// const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000; +// const word = timestampWord.get(ms); +// if (!word) { +// return; +// } +// const entry = wordInkDocMap.get(word); +// if (entry) { +// entry.push(inkDoc); +// wordInkDocMap.set(word, entry); +// } else { +// const newEntry = [inkDoc]; +// wordInkDocMap.set(word, newEntry); +// } +// }); +// if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap); +// } +// } +// const text = exports['text/plain']; + +// if (this.currGroup) { +// this.currGroup.transcription = text; +// this.currGroup.title = text.split('\n')[0]; +// } + +// ref.editor.clear(); +// } +// } +// }; + +// /** +// * Creates the ink grouping once the user leaves the writing mode. +// */ +// createInkGroup() { +// // TODO nda - if document being added to is a inkGrouping then we can just add to that group +// if (Doc.ActiveTool === InkTool.Write) { +// CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { +// // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those +// const selected = ffView.unprocessedDocs; +// const newCollection = this.groupInkDocs( +// selected.filter(doc => doc.embedContainer), +// ffView +// ); +// ffView.unprocessedDocs = []; + +// InkTranscription.Instance.transcribeInk(newCollection, selected, false); +// }); +// } +// CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); +// } + +// /** +// * Creates the groupings for a given list of ink docs on a specific doc view +// * @param selected: the list of ink docs to create a grouping of +// * @param docView: the view in which we want the grouping to be created +// * @param word: optional param if the group we are creating is a word (subgrouping individual words) +// * @returns a new collection Doc or undefined if the grouping fails +// */ +// groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined { +// const bounds: { x: number; y: number; width?: number; height?: number }[] = []; + +// // calculate the necessary bounds from the selected ink docs +// selected.map( +// action(d => { +// const x = NumCast(d.x); +// const y = NumCast(d.y); +// const width = NumCast(d._width); +// const height = NumCast(d._height); +// bounds.push({ x, y, width, height }); +// }) +// ); + +// // calculate the aggregated bounds +// const aggregBounds = aggregateBounds(bounds, 0, 0); +// const marqViewRef = docView._marqueeViewRef.current; + +// // set the vals for bounds in marqueeView +// if (marqViewRef) { +// marqViewRef._downX = aggregBounds.x; +// marqViewRef._downY = aggregBounds.y; +// marqViewRef._lastX = aggregBounds.r; +// marqViewRef._lastY = aggregBounds.b; +// } + +// // map through all the selected ink strokes and create the groupings +// selected.map( +// action(d => { +// const dx = NumCast(d.x); +// const dy = NumCast(d.y); +// delete d.x; +// delete d.y; +// delete d.activeFrame; +// delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection +// delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection +// // calculate pos based on bounds +// if (marqViewRef?.Bounds) { +// d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; +// d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; +// } +// return d; +// }) +// ); +// docView.props.removeDocument?.(selected); +// // Gets a collection based on the selected nodes using a marquee view ref +// const newCollection = marqViewRef?.getCollection(selected, undefined, true); +// if (newCollection) { +// newCollection.width = NumCast(newCollection._width); +// newCollection.height = NumCast(newCollection._height); +// // if the grouping we are creating is an individual word +// if (word) { +// newCollection.title = word; +// } +// } + +// // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs +// newCollection && docView.props.addDocument?.(newCollection); +// return newCollection; +// } + +// render() { +// return ( +// <div className="ink-transcription"> +// <div className="math-editor" ref={this.setMathRef} touch-action="none"></div> +// <div className="text-editor" ref={this.setTextRef} touch-action="none"></div> +// </div> +// ); +// } +// } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index e081961e5..41a2507f9 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -20,7 +20,7 @@ Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class */ -import React = require('react'); +import * as React from 'react'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; @@ -36,7 +36,6 @@ import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; import { ViewBoxBaseComponent } from './DocComponent'; -import { INK_MASK_SIZE } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; import './InkStroke.scss'; @@ -47,7 +46,8 @@ import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from './nodes/trails'; import { StyleProp } from './StyleProvider'; -import Color = require('color'); +// import { INK_MASK_SIZE } from './global/globalCssVariables.scss'; +const INK_MASK_SIZE = 1000; @observer export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx index 13d52db88..e996fc946 100644 --- a/src/client/views/KeyphraseQueryView.tsx +++ b/src/client/views/KeyphraseQueryView.tsx @@ -1,6 +1,6 @@ -import { observer } from "mobx-react"; -import React = require("react"); -import "./KeyphraseQueryView.scss"; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import './KeyphraseQueryView.scss'; // tslint:disable-next-line: class-name export interface KP_Props { @@ -8,7 +8,7 @@ export interface KP_Props { } @observer -export class KeyphraseQueryView extends React.Component<KP_Props>{ +export class KeyphraseQueryView extends React.Component<KP_Props> { constructor(props: KP_Props) { super(props); } @@ -22,13 +22,17 @@ export class KeyphraseQueryView extends React.Component<KP_Props>{ <form> {keyterms.map((kp: string) => { //return (<p>{"-" + kp}</p>); - return (<p><label> - <input name="query" type="radio" /> - <span>{kp}</span> - </label></p>); + return ( + <p> + <label> + <input name="query" type="radio" /> + <span>{kp}</span> + </label> + </p> + ); })} </form> </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 58c1570b2..50feccce2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import 'browndash-components/dist/styles/global.min.css'; +import '../../../node_modules/browndash-components/dist/styles/global.min.css'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import 'normalize.css'; @@ -43,9 +43,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; -import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss'; import { KeyManager } from './GlobalKeyHandler'; -import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import './MainView.scss'; @@ -71,6 +69,9 @@ import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; +// import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss'; +const LEFT_MENU_WIDTH = '60px'; +const TOPBAR_HEIGHT = '37px'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -1063,7 +1064,7 @@ export class MainView extends React.Component { <MarqueeOptionsMenu /> <TimelineMenu /> <RichTextMenu /> - <InkTranscription /> + {/* <InkTranscription /> */} {this.snapLines} <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> <OverlayView /> diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 70a44a08a..a241e3a2b 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -15,7 +15,7 @@ import './MarqueeAnnotator.scss'; import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { AnchorMenu } from './pdf/AnchorMenu'; -import React = require('react'); +import * as React from 'react'; const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index bcbdd3ccb..5c6912121 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -156,6 +156,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps> { <span>Key:</span> <div className="metadataEntry-autoSuggester" onClick={e => this.autosuggestRef.current!.input?.focus()}> <Autosuggest + // @ts-ignore inputProps={{ value: this._currentKey, onChange: this.onKeyChange }} getSuggestionValue={this.getSuggestionValue} suggestions={emptyPath} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 6635aabf9..7824ad1c8 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; import './PropertiesButtons.scss'; -import React = require('react'); +import * as React from 'react'; enum UtilityButtonState { Default, diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx index bd586b2f9..2b9bfb505 100644 --- a/src/client/views/PropertiesSection.tsx +++ b/src/client/views/PropertiesSection.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index c5b0528af..2c3cd8eac 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1,13 +1,13 @@ -import React = require('react'); +import * as React from 'react'; import { IconLookup } from '@fortawesome/fontawesome-svg-core'; import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Checkbox, Tooltip } from '@material-ui/core'; +import { Checkbox, Tooltip } from '@mui/material'; import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components'; import { concat } from 'lodash'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { ColorState, SketchPicker } from 'react-color'; +import { ColorResult, SketchPicker } from 'react-color'; import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols'; @@ -875,7 +875,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return ( <SketchPicker onChange={undoable( - action((color: ColorState) => setter(color.hex)), + action((color: ColorResult) => setter(color.hex)), 'set stroke color property' )} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 049ba4841..2a52d86c2 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -16,7 +16,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import './SidebarAnnos.scss'; import { StyleProp } from './StyleProvider'; -import React = require('react'); +import * as React from 'react'; interface ExtraProps { fieldKey: string; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 806c9c001..4a08081a1 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -1,6 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from 'browndash-components'; import { action, runInAction, untracked } from 'mobx'; import { extname } from 'path'; @@ -23,7 +23,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PropertiesView } from './PropertiesView'; import './StyleProvider.scss'; -import React = require('react'); +import * as React from 'react'; export enum StyleProp { TreeViewIcon = 'treeView_Icon', @@ -320,9 +320,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps color={SettingsManager.userColor} background={showFilterIcon} items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])] - .filter(dv => StrListCast(dv.Document.childFilters).length || StrListCast(dv.Document.childRangeFilters).length) + .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length) .map(dv => ({ - text: StrCast(dv.Document.title), + text: StrCast(dv?.Document.title), val: dv as any, style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor}, } as IListItemProps)) } diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 6da228417..48653f30a 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -13,7 +13,7 @@ import { CollectionTreeView } from './collections/CollectionTreeView'; import { DocumentView } from './nodes/DocumentView'; import { DefaultStyleProvider } from './StyleProvider'; import './TemplateMenu.scss'; -import React = require('react'); +import * as React from 'react'; @observer class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> { diff --git a/src/client/views/TouchScrollableMenu.tsx b/src/client/views/TouchScrollableMenu.tsx index 969605be9..530c693a7 100644 --- a/src/client/views/TouchScrollableMenu.tsx +++ b/src/client/views/TouchScrollableMenu.tsx @@ -1,6 +1,6 @@ -import React = require("react"); -import { computed } from "mobx"; -import { observer } from "mobx-react"; +import * as React from 'react'; +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; export interface TouchScrollableMenuProps { options: JSX.Element[]; @@ -24,31 +24,35 @@ export interface TouchScrollableMenuItemProps { @observer export default class TouchScrollableMenu extends React.Component<TouchScrollableMenuProps> { - @computed - private get possibilities() { return this.props.options; } + private get possibilities() { + return this.props.options; + } @computed - private get selectedIndex() { return this.props.selectedIndex; } + private get selectedIndex() { + return this.props.selectedIndex; + } render() { return ( - <div className="inkToTextDoc-cont" style={{ - transform: `translate(${this.props.x}px, ${this.props.y}px)`, - width: 300, - height: this.possibilities.length * 25 - }}> + <div + className="inkToTextDoc-cont" + style={{ + transform: `translate(${this.props.x}px, ${this.props.y}px)`, + width: 300, + height: this.possibilities.length * 25, + }}> <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-this.selectedIndex * 25}px)` }}> {this.possibilities} </div> - <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}> - </div> + <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}></div> </div> ); } } -export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenuItemProps>{ +export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenuItemProps> { render() { return ( <div className="menuItem-cont" onClick={this.props.onClick}> @@ -56,4 +60,4 @@ export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenu </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index d6368464a..330cb93e4 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -10,11 +10,13 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { StyleProp } from '../StyleProvider'; -import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss'; import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; import './CollectionCarousel3DView.scss'; import { CollectionSubView } from './CollectionSubView'; - +// import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss'; +const CAROUSEL3D_CENTER_SCALE = '1'; +const CAROUSEL3D_SIDE_SCALE = '1'; +const CAROUSEL3D_TOP = '0'; @observer export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 333ba9f32..11543bbe5 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -276,8 +276,6 @@ z-index: 20; } /*# sourceMappingURL=goldenlayout-base.css.map */ -@import '../../../../node_modules/golden-layout/src/css/goldenlayout-dark-theme.css'; - .lm_title { -webkit-appearance: none; display: inline-block; @@ -681,11 +679,6 @@ ul.lm_tabs::before { height: 8px; } - .flexlayout__tab_button:hover .flexlayout__tab_button_trailing, - .flexlayout__tab_button--selected .flexlayout__tab_button_trailing { - background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center; - } - .flexlayout__tab_button_overflow { float: left; width: 20px; @@ -696,7 +689,6 @@ ul.lm_tabs::before { font-size: 10px; color: lightgray; font-family: Arial, sans-serif; - background: transparent url('../../../../node_modules/flexlayout-react/images/more.png') no-repeat left; } .flexlayout__tabset_header { @@ -751,7 +743,6 @@ ul.lm_tabs::before { height: 20px; border: none; outline-width: 0; - background: transparent url('../../../../node_modules/flexlayout-react/images/maximize.png') no-repeat center; } .flexlayout__tab_toolbar_button-max { @@ -759,7 +750,6 @@ ul.lm_tabs::before { height: 20px; border: none; outline-width: 0; - background: transparent url('../../../../node_modules/flexlayout-react/images/restore.png') no-repeat center; } .flexlayout__popup_menu_item { @@ -877,11 +867,6 @@ ul.lm_tabs::before { height: 8px; } - .flexlayout__border_button:hover .flexlayout__border_button_trailing, - .flexlayout__border_button--selected .flexlayout__border_button_trailing { - background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center; - } - .flexlayout__border_toolbar_left { position: absolute; display: flex; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 2e8047309..858090c05 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -30,7 +30,7 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; -import React = require('react'); +import * as React from 'react'; import { SettingsManager } from '../../util/SettingsManager'; const _global = (window /* browser */ || global) /* node */ as any; diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index a9b5f401d..61a5738ec 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -18,9 +18,9 @@ import { EditableView } from '../EditableView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { CollectionStackingView } from './CollectionStackingView'; import './CollectionStackingView.scss'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; +// import * as higflyout from '@hig/flyout'; +// export const { anchorPoints } = higflyout; +// export const Flyout = higflyout.default; interface CMVFieldRowProps { rows: () => number; @@ -338,11 +338,11 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr )} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : ( <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}> - <Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={this.renderMenu()}> + {/* <Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={this.renderMenu()}> <button className="collectionStackingView-sectionOptionButton"> <FontAwesomeIcon icon="ellipsis-v" size="lg" /> </button> - </Flyout> + </Flyout> */} </div> )} </div> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 8b3531da8..3edf4135f 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,6 +1,6 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -541,6 +541,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi autosuggestProps: { inputProps: { value: this._currentKey, + // @ts-ignore onChange: this.onKeyChange, }, getSuggestionValue: this.getSuggestionValue, diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index ad5431c8e..ba00c4fa0 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -1,5 +1,4 @@ -import React = require('react'); -import { CursorProperty } from 'csstype'; +import * as React from 'react'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Field, Opt } from '../../../fields/Doc'; @@ -44,7 +43,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { notetakingCategoryField = 'NotetakingCategory'; public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; - @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; @computed get chromeHidden() { return BoolCast(this.layoutDoc.chromeHidden) || this.props.onBrowseClick?.() ? true : false; diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 9e5f09479..9be01df18 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index ad8144466..c5677e2ba 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -12,7 +12,7 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm' import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import './CollectionPileView.scss'; import { CollectionSubView } from './CollectionSubView'; -import React = require('react'); +import * as React from 'react'; @observer export class CollectionPileView extends CollectionSubView() { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7fee1cda6..92b5470ae 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 59e0c60b9..bc428c22f 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,6 +1,6 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CursorProperty } from 'csstype'; +import * as CSS from 'csstype'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; @@ -59,7 +59,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map<string, number>(); // Assuming that this is the current css cursor style - @observable _cursor: CursorProperty = 'ew-resize'; + @observable _cursor: CSS.Property.Cursor = 'ew-resize'; // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling // does this mean whether the browser is hidden? Or is chrome something else entirely? diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 9bd412fb8..3282eb4b4 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index f082ca0da..40b2f9644 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -24,7 +24,7 @@ import { ViewBoxBaseComponent } from '../DocComponent'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { LoadingBox } from '../nodes/LoadingBox'; import { CollectionView, CollectionViewProps } from './CollectionView'; -import React = require('react'); +import * as React from 'react'; export interface SubCollectionViewProps extends CollectionViewProps { isAnyChildContentActive: () => boolean; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 8d114761a..e65f24702 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -21,7 +21,7 @@ import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './coll import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; -import React = require('react'); +import * as React from 'react'; @observer export class CollectionTimeView extends CollectionSubView() { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 1315decb2..5c165fe70 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -26,7 +26,7 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; -import React = require('react'); +import * as React from 'react'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index def4beca3..4a1d702b8 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -33,7 +33,7 @@ import { CollectionDockingView } from './CollectionDockingView'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; -import React = require('react'); +import * as React from 'react'; import { Docs } from '../../documents/Documents'; import { ComputedField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 0de1068f7..ac79e4fef 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -24,7 +24,6 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; -import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; @@ -35,7 +34,9 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; -import React = require('react'); +import * as React from 'react'; +// import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; +const TREE_BULLET_WIDTH = '10px'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -118,16 +119,16 @@ export class TreeView extends React.Component<TreeViewProps> { return this.doc._type_collection === CollectionViewType.Docking ? this.fieldKey : this.props.treeView.dashboardMode - ? this.fieldKey - : this.props.treeView.fileSysMode - ? this.doc.isFolder - ? this.fieldKey - : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list - : this.props.treeView.outlineMode || this.childDocs - ? this.fieldKey - : Doc.noviceMode - ? 'layout' - : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields'); + ? this.fieldKey + : this.props.treeView.fileSysMode + ? this.doc.isFolder + ? this.fieldKey + : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list + : this.props.treeView.outlineMode || this.childDocs + ? this.fieldKey + : Doc.noviceMode + ? 'layout' + : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields'); } @computed get doc() { @@ -832,14 +833,14 @@ export class TreeView extends React.Component<TreeViewProps> { ...(this.doc.isFolder ? folderOp : Doc.IsSystem(this.doc) - ? [] - : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) - ? [openEmbedding, makeFolder] - : this.doc._type_collection === CollectionViewType.Docking - ? [] - : this.props.treeView.Document === Doc.MyRecentlyClosed - ? [reopenDoc] - : [openEmbedding, focusDoc]), + ? [] + : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) + ? [openEmbedding, makeFolder] + : this.doc._type_collection === CollectionViewType.Docking + ? [] + : this.props.treeView.Document === Doc.MyRecentlyClosed + ? [reopenDoc] + : [openEmbedding, focusDoc]), ]; }; childContextMenuItems = () => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx index 00505dbe3..99ee5ef4e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx @@ -2,7 +2,7 @@ import { observer } from 'mobx-react'; import { Doc } from '../../../../fields/Doc'; import { NumCast } from '../../../../fields/Types'; import './CollectionFreeFormView.scss'; -import React = require('react'); +import * as React from 'react'; export interface CollectionFreeFormViewBackgroundGridProps { panX: () => number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 3af5a6c4b..3ba7aedef 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -1,7 +1,7 @@ import { IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import './CollectionFreeFormView.scss'; -import React = require('react'); /** * An Fsa Arc. The first array element is a test condition function that will be observed. diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index daf1bd8a7..1265dc2de 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -4,7 +4,7 @@ import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { PresBox } from '../../nodes/trails/PresBox'; import './CollectionFreeFormView.scss'; -import React = require('react'); +import * as React from 'react'; import { CollectionFreeFormView } from './CollectionFreeFormView'; import { NumCast } from '../../../../fields/Types'; import { LinkManager } from '../../../util/LinkManager'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index d6a738084..b8c0967c1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -5,7 +5,7 @@ import { RefField } from '../../../../fields/RefField'; import { listSpec } from '../../../../fields/Schema'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { aggregateBounds } from '../../../../Utils'; -import React = require('react'); +import * as React from 'react'; export interface ViewDefBounds { type: string; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 0c0d45a30..4995925c8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -13,7 +13,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; import './CollectionFreeFormLinkView.scss'; -import React = require('react'); +import * as React from 'react'; export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -32,7 +32,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo componentWillUnmount() { this._anchorDisposer?.(); } - @action timeout = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25))); + @action timeout: any = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25))); componentDidMount() { this._anchorDisposer = reaction( () => [ diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 420e6a318..779a0bf96 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -5,7 +5,7 @@ import { DocumentManager } from '../../../util/DocumentManager'; import { LightboxView } from '../../LightboxView'; import './CollectionFreeFormLinksView.scss'; import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; -import React = require('react'); +import * as React from 'react'; @observer export class CollectionFreeFormLinksView extends React.Component { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index 38aeea152..f54726a00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -4,7 +4,7 @@ import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { PresBox } from '../../nodes/trails/PresBox'; import './CollectionFreeFormView.scss'; -import React = require('react'); +import * as React from 'react'; import { CollectionFreeFormView } from './CollectionFreeFormView'; export interface CollectionFreeFormPannableContentsProps { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 9e8d92d7d..45e24bbb2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -9,8 +9,8 @@ import { listSpec } from '../../../../fields/Schema'; import { Cast } from '../../../../fields/Types'; import { CollectionViewProps } from '../CollectionView'; import './CollectionFreeFormView.scss'; -import React = require('react'); -import v5 = require('uuid/v5'); +import * as React from 'react'; +import * as uuid from 'uuid'; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { @@ -42,7 +42,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV if (el) { const ctx = el.getContext('2d'); if (ctx) { - ctx.fillStyle = '#' + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + '22'; + ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22'; ctx.fillRect(0, 0, 20, 20); ctx.fillStyle = 'black'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 91617ec44..03d302f39 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,6 +3,7 @@ import { Colors } from 'browndash-components'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; +import * as React from 'react'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; @@ -45,13 +46,12 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; +import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; -import React = require('react'); -import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; export type collectionFreeformViewProps = { NativeWidth?: () => number; @@ -815,23 +815,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top ) - .reduce((intersections, { inkStroke, inkView }) => { - const { inkData } = inkStroke.inkScaledData(); - // Convert from screen space to ink space for the intersection. - const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); - const currPointInkSpace = inkStroke.ptFromScreen(currPoint); - for (var i = 0; i < inkData.length - 3; i += 4) { - const rawIntersects = InkField.Segment(inkData, i).intersects({ - // compute all unique intersections - p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, - p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, - }); - const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type - // return tuples of the inkingStroke intersected, and the t value of the intersection - intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve - } - return intersections; - }, [] as { t: number; inkView: DocumentView }[]); + .reduce( + (intersections, { inkStroke, inkView }) => { + const { inkData } = inkStroke.inkScaledData(); + // Convert from screen space to ink space for the intersection. + const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); + const currPointInkSpace = inkStroke.ptFromScreen(currPoint); + for (var i = 0; i < inkData.length - 3; i += 4) { + const rawIntersects = InkField.Segment(inkData, i).intersects({ + // compute all unique intersections + p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, + }); + const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type + // return tuples of the inkingStroke intersected, and the t value of the intersection + intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve + } + return intersections; + }, + [] as { t: number; inkView: DocumentView }[] + ); }; /** diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 607f9fb95..825bd5f19 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -1,14 +1,11 @@ -import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { observer } from 'mobx-react'; -import { unimplementedFunction } from '../../../../Utils'; -import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { IconButton } from 'browndash-components'; -import { StrCast } from '../../../../fields/Types'; -import { Doc } from '../../../../fields/Doc'; import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { unimplementedFunction } from '../../../../Utils'; import { SettingsManager } from '../../../util/SettingsManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 18b9b4423..330c13e97 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -24,7 +24,7 @@ import { PreviewCursor } from '../../PreviewCursor'; import { SubCollectionViewProps } from '../CollectionSubView'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; -import React = require('react'); +import * as React from 'react'; import { freeformScrollMode } from '../../../util/SettingsManager'; interface MarqueeViewProps { diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index d0c14a21d..3c2f5ccd7 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -18,9 +18,9 @@ user-select: none; } - > input:not(:checked) ~ &.true { - background-color: transparent; - } + // > input:not(:checked) ~ &.true { + // background-color: transparent; + // } .collectionLinearView { display: flex; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index a367e3e73..0ee5f3b40 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 030f9953c..029d39f88 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, ObservableMap, observe } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 65e47f441..0e37198bb 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 03697b40a..53f4a4d51 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { IconButton, Size } from 'browndash-components'; import { computed } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 0ad76db35..de29d8602 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -14,7 +14,7 @@ import { undoable, UndoManager } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { GestureOverlay } from '../GestureOverlay'; import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; -import { InkTranscription } from '../InkTranscription'; +// import { InkTranscription } from '../InkTranscription'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @@ -293,14 +293,14 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { // TODO: nda - will probably need to go through and only remove the unprocessed selected docs ffView.unprocessedDocs = []; - InkTranscription.Instance.transcribeInk(newCollection, selected, false); + // InkTranscription.Instance.transcribeInk(newCollection, selected, false); }); } CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) { - InkTranscription.Instance?.createInkGroup(); + // InkTranscription.Instance?.createInkGroup(); if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures) diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 3082c632d..3d9cf1893 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -6,7 +6,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenu.scss'; import { LinkMenuGroup } from './LinkMenuGroup'; -import React = require('react'); +import * as React from 'react'; import { SettingsManager } from '../../util/SettingsManager'; interface Props { diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 5453ed07b..91142b90b 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -7,7 +7,7 @@ import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import './LinkMenu.scss'; import { LinkMenuItem } from './LinkMenuItem'; -import React = require('react'); +import * as React from 'react'; import { DocumentType } from '../../documents/DocumentTypes'; interface LinkMenuGroupProps { diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 611771fff..12db7fa4b 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -1,6 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; @@ -17,7 +17,7 @@ import { undoBatch } from '../../util/UndoManager'; import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; -import React = require('react'); +import * as React from 'react'; interface LinkMenuItemProps { groupType: string; diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index a36c8d778..5460a6daf 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -10,7 +10,7 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { SearchBox } from '../search/SearchBox'; import { DefaultStyleProvider } from '../StyleProvider'; import './LinkPopup.scss'; -import React = require('react'); +import * as React from 'react'; interface LinkPopupProps { linkFrom?: () => Doc | undefined; diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx index 9662b2fea..3e7f5be74 100644 --- a/src/client/views/linking/LinkRelationshipSearch.tsx +++ b/src/client/views/linking/LinkRelationshipSearch.tsx @@ -1,6 +1,6 @@ import { observer } from 'mobx-react'; import './LinkEditor.scss'; -import React = require('react'); +import * as React from 'react'; interface link_relationshipSearchProps { results: string[] | undefined; diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx index a085e4a66..23027808f 100644 --- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx +++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx @@ -25,11 +25,13 @@ export const Recommendation = (props: IRecommendation) => { switch (type) { case 'YouTube': console.log('create ', type, 'document'); - doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript }); + doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315 }); + doc.transcript = transcript ? JSON.stringify(transcript) : undefined; break; case 'Video': console.log('create ', type, 'document'); - doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript }); + doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315 }); + doc.transcript = transcript ? JSON.stringify(transcript) : undefined; break; case 'Webpage': console.log('create ', type, 'document'); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index dacdbe986..1fb26c99b 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,6 +1,6 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { DateField } from '../../../fields/DateField'; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 6195a155d..624f28413 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -14,7 +14,7 @@ import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; -import React = require('react'); +import * as React from 'react'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps { diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 2a20d935b..b4ba51814 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -1,9 +1,8 @@ -import React = require('react'); +import * as React from 'react'; import { action } from 'mobx'; import { observer } from 'mobx-react'; -import { ColorState, SketchPicker } from 'react-color'; +import { ColorResult, SketchPicker } from 'react-color'; import { Doc } from '../../../fields/Doc'; -import { Height } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { NumCast, StrCast } from '../../../fields/Types'; import { DashColor } from '../../../Utils'; @@ -25,7 +24,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() { @undoBatch @action - static switchColor(color: ColorState) { + static switchColor(color: ColorResult) { SetActiveInkColor(color.hex); SelectionManager.Views().map(view => { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 9fd4fbcaa..72a473114 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -13,7 +13,7 @@ import './ComparisonBox.scss'; import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; -import React = require('react'); +import * as React from 'react'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 4dbd67485..c2e03744e 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -1,3 +1,4 @@ +import { Checkbox } from '@mui/material'; import { ColorPicker, EditableText, Size, Type } from 'browndash-components'; import * as d3 from 'd3'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; @@ -12,7 +13,6 @@ import { Docs } from '../../../../documents/Documents'; import { undoable } from '../../../../util/UndoManager'; import { PinProps, PresBox } from '../../trails'; import './Chart.scss'; -import { Checkbox } from '@material-ui/core'; export interface PieChartProps { Document: Doc; @@ -277,6 +277,7 @@ export class PieChart extends React.Component<PieChartProps> { return 'slice'; } ) + // @ts-ignore .attr('d', arc) .on('click', onPointClick) .on('mouseover', onHover) diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 9ac06cf3c..d422a7536 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -10,8 +10,9 @@ import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils'; import { DragManager } from '../../../../util/DragManager'; import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; -import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss'; import './Chart.scss'; +//import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss'; +const DATA_VIZ_TABLE_ROW_HEIGHT = '30px'; interface TableBoxProps { Document: Doc; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 4b58bfe48..54cfba506 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,6 +1,5 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; -import { MouseEventHandler } from 'react-select'; import { Doc, Opt } from '../../../fields/Doc'; import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { ScriptField } from '../../../fields/ScriptField'; @@ -46,7 +45,7 @@ import { ScriptingBox } from './ScriptingBox'; import { PresBox } from './trails/PresBox'; import { VideoBox } from './VideoBox'; import { WebBox } from './WebBox'; -import React = require('react'); +import * as React from 'react'; import XRegExp = require('xregexp'); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -171,7 +170,7 @@ export class DocumentContentsView extends React.Component< ...this.props, Document: this.layoutDoc ?? this.props.Document, TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc, - onClick: onClick as any as MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag + onClick: onClick as any as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag onInput: onInput as any as React.FormEventHandler, }; return { diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index 49592d993..e8d8c05e3 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -4,7 +4,7 @@ import { DocumentView } from './DocumentView'; import { DocumentManager } from '../../util/DocumentManager'; import { Transformer, ts } from '../../util/Scripting'; import { Field } from '../../../fields/Doc'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, observable } from 'mobx'; import { Id } from '../../../fields/FieldSymbols'; import { factory } from 'typescript'; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index ce3650749..90b044374 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; @@ -15,8 +15,8 @@ import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { TaskCompletionBox } from './TaskCompletedBox'; import { PinProps } from './trails'; -import React = require('react'); -import _ = require('lodash'); +import * as React from 'react'; +import * as _ from 'lodash'; interface DocumentLinksButtonProps { View: DocumentView; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 40ae1459f..f2a910023 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -51,7 +51,7 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; -import React = require('react'); +import * as React from 'react'; const { Howl } = require('howler'); interface Window { diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index cd263f82a..23413b9d1 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -1,4 +1,4 @@ -import EquationEditor from 'equation-editor-react'; +import EquationEditor from './formattedText/EquationEditor'; import { action, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx index 20afa4565..8d03bf57a 100644 --- a/src/client/views/nodes/FaceRectangle.tsx +++ b/src/client/views/nodes/FaceRectangle.tsx @@ -1,14 +1,14 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import { observable, runInAction } from "mobx"; -import { RectangleTemplate } from "./FaceRectangles"; +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { observable, runInAction } from 'mobx'; +import { RectangleTemplate } from './FaceRectangles'; @observer export default class FaceRectangle extends React.Component<{ rectangle: RectangleTemplate }> { @observable private opacity = 0; componentDidMount() { - setTimeout(() => runInAction(() => this.opacity = 1), 500); + setTimeout(() => runInAction(() => (this.opacity = 1)), 500); } render() { @@ -18,12 +18,11 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl style={{ ...rectangle.style, opacity: this.opacity, - transition: "1s ease opacity", - position: "absolute", - borderRadius: 5 + transition: '1s ease opacity', + position: 'absolute', + borderRadius: 5, }} /> ); } - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx index 0d1e063af..26e720c0d 100644 --- a/src/client/views/nodes/FaceRectangles.tsx +++ b/src/client/views/nodes/FaceRectangles.tsx @@ -1,9 +1,9 @@ -import React = require("react"); -import { Doc, DocListCast } from "../../../fields/Doc"; -import { Cast, NumCast } from "../../../fields/Types"; -import { observer } from "mobx-react"; -import { Id } from "../../../fields/FieldSymbols"; -import FaceRectangle from "./FaceRectangle"; +import * as React from 'react'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { Cast, NumCast } from '../../../fields/Types'; +import { observer } from 'mobx-react'; +import { Id } from '../../../fields/FieldSymbols'; +import FaceRectangle from './FaceRectangle'; interface FaceRectanglesProps { document: Doc; @@ -18,7 +18,6 @@ export interface RectangleTemplate { @observer export class FaceRectangles extends React.Component<FaceRectanglesProps> { - render() { const faces = DocListCast(this.props.document.faces); const templates: RectangleTemplate[] = faces.map(faceDoc => { @@ -33,14 +32,15 @@ export class FaceRectangles extends React.Component<FaceRectanglesProps> { } as React.CSSProperties; return { id: rectangle[Id], - style: style + style: style, }; }); return ( <div> - {templates.map(rectangle => <FaceRectangle key={rectangle.id} rectangle={rectangle} />)} + {templates.map(rectangle => ( + <FaceRectangle key={rectangle.id} rectangle={rectangle} /> + ))} </div> ); } - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 219e3025a..f4c5167a5 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { computed } from 'mobx'; import { observer } from 'mobx-react'; import { DateField } from '../../../fields/DateField'; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d28d71fe3..dbfeec1c3 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; @@ -35,7 +35,7 @@ import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import { PinProps, PresBox } from './trails'; -import React = require('react'); +import * as React from 'react'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 0c6377c3a..d2325a807 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -20,7 +20,7 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { ImageBox } from './ImageBox'; import './KeyValueBox.scss'; import { KeyValuePair } from './KeyValuePair'; -import React = require('react'); +import * as React from 'react'; export type KVPScript = { script: CompiledScript; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 577685636..3cda70648 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -12,9 +12,9 @@ import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; -import React = require('react'); +import * as React from 'react'; import { DocCast } from '../../../fields/Types'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { DocumentOptions, FInfo } from '../../documents/Documents'; // Represents one row in a key value plane diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index e1421878b..71f9c000b 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -84,7 +84,18 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); } - fitTextToBox = (r: any) => { + fitTextToBox = (r: any): NodeJS.Timeout | { + rotateText: null; + fontSizeFactor: number; + minimumFontSize: number; + maximumFontSize: number; + limitingDimension: string; + horizontalAlign: string; + verticalAlign: string; + textAlign: string; + singleLine: boolean; + whiteSpace: string; + } => { const singleLine = BoolCast(this.layoutDoc._singleLine, true); const params = { rotateText: null, @@ -143,9 +154,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp paddingBottom: NumCast(this.layoutDoc._yPadding), width: this.props.PanelWidth(), height: this.props.PanelHeight(), - whiteSpace: typeof boxParams !== 'number' && boxParams.singleLine ? 'pre' : 'pre-wrap', + whiteSpace: !(boxParams instanceof NodeJS.Timeout) && boxParams.singleLine ? 'pre' : 'pre-wrap', }}> - <span style={{ width: typeof boxParams !== 'number' && boxParams.singleLine ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}> + <span style={{ width: !(boxParams instanceof NodeJS.Timeout) && boxParams.singleLine ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}> {label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')} </span> </div> diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index cf157c3a9..1b5161e47 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -12,9 +12,9 @@ import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; -import React = require('react'); -import globalCssVariables = require('../global/globalCssVariables.scss'); - +import * as React from 'react'; +// import globalCssVariables = require('../global/globalCssVariables.scss'); +const MEDIUM_GRAY = 'lightGray'; @observer export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { @@ -77,8 +77,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_1') ? 'link_anchor_1' : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_2') - ? 'link_anchor_2' - : ''; + ? 'link_anchor_2' + : ''; return ( <div ref={this._ref} @@ -97,7 +97,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} style={{ - border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined, + border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${MEDIUM_GRAY} 2px` : undefined, background, left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`, top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`, diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 743075c89..ed448ecfb 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { Bezier } from 'bezier-js'; import { computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index c45045a8a..32300d60a 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index ad5324b3e..4df8e792e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import wiki from 'wikijs'; @@ -18,7 +18,7 @@ import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView'; import './LinkDocPreview.scss'; -import React = require('react'); +import * as React from 'react'; interface LinkDocPreviewProps { linkDoc?: Doc; diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index f6680aac0..e6c1db0af 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IReactionDisposer, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 398d1255e..69723b171 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -794,17 +794,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps /> ))} </div> - {/* <MapBoxInfoWindow - key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]} - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} - place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})} - markerMap={this.markerMap} - PanelWidth={this.infoWidth} - PanelHeight={this.infoHeight} - moveDocument={this.moveDocument} - isAnyChildContentActive={this.isAnyChildContentActive} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - /> */} </div> {/* </LoadScript > */} <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx index a77bfc50a..39ed6a47e 100644 --- a/src/client/views/nodes/MapBox/MapBox2.tsx +++ b/src/client/views/nodes/MapBox/MapBox2.tsx @@ -1,597 +1,597 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; -import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; -import { Id } from '../../../../fields/FieldSymbols'; -import { NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; -import { Docs } from '../../../documents/Documents'; -import { DragManager } from '../../../util/DragManager'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { UndoManager } from '../../../util/UndoManager'; -import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; -import { Colors } from '../../global/globalEnums'; -import { AnchorMenu } from '../../pdf/AnchorMenu'; -import { Annotation } from '../../pdf/Annotation'; -import { SidebarAnnos } from '../../SidebarAnnos'; -import { FieldView, FieldViewProps } from '../FieldView'; -import { PinProps } from '../trails'; -import './MapBox2.scss'; -import { MapBoxInfoWindow } from './MapBoxInfoWindow'; - -/** - * MapBox2 architecture: - * Main component: MapBox2.tsx - * Supporting Components: SidebarAnnos, CollectionStackingView - * - * MapBox2 is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content. - * The main body of MapBox2 uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view. - * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available, - * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map). - * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts). - * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps - */ - -// const _global = (window /* browser */ || global /* node */) as any; - -const mapContainerStyle = { - height: '100%', -}; - -const defaultCenter = { - lat: 42.360081, - lng: -71.058884, -}; - -const mapOptions = { - fullscreenControl: false, -}; - -const apiKey = process.env.GOOGLE_MAPS; - -const script = document.createElement('script'); -script.defer = true; -script.async = true; -script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`; -console.log(script.src); -document.head.appendChild(script); - -/** - * Consider integrating later: allows for drawing, circling, making shapes on map - */ -// const drawingManager = new window.google.maps.drawing.DrawingManager({ -// drawingControl: true, -// drawingControlOptions: { -// position: google.maps.ControlPosition.TOP_RIGHT, -// drawingModes: [ -// google.maps.drawing.OverlayType.MARKER, -// // currently we are not supporting the following drawing mode on map, a thought for future development -// google.maps.drawing.OverlayType.CIRCLE, -// google.maps.drawing.OverlayType.POLYLINE, -// ], -// }, -// }); - -// options for searchbox in Google Maps Places Autocomplete API -const options = { - fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields - strictBounds: false, - types: ['establishment'], // type pf places, subject of change according to user need -} as google.maps.places.AutocompleteOptions; - -@observer -export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() { - private _dropDisposer?: DragManager.DragDropDisposer; - private _disposers: { [name: string]: IReactionDisposer } = {}; - private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); - @observable private _overlayAnnoInfo: Opt<Doc>; - showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno)); - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(MapBox2, fieldKey); - } - public get SidebarKey() { - return this.fieldKey + '_sidebar'; - } - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void); - @computed get inlineTextAnnotations() { - return this.allMapMarkers.filter(a => a.text_inlineAnnotations); - } - - @observable private _map: google.maps.Map = null as unknown as google.maps.Map; - @observable private selectedPlace: Doc | undefined; - @observable private markerMap: { [id: string]: google.maps.Marker } = {}; - @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter; - @observable private inputRef = React.createRef<HTMLInputElement>(); - @observable private searchMarkers: google.maps.Marker[] = []; - @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options); - @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); - @computed get allSidebarDocs() { - return DocListCast(this.dataDoc[this.SidebarKey]); - } - @computed get allMapMarkers() { - return DocListCast(this.dataDoc[this.annotationKey]); - } - @observable private toggleAddMarker = false; - - @observable _showSidebar = false; - @computed get SidebarShown() { - return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false; - } - - static _canAnnotate = true; - static _hadSelection: boolean = false; - private _sidebarRef = React.createRef<SidebarAnnos>(); - private _ref: React.RefObject<HTMLDivElement> = React.createRef(); - - componentDidMount() { - this.props.setContentView?.(this); - } - - @action - private setSearchBox = (searchBox: any) => { - this.searchBox = searchBox; - }; - - // iterate allMarkers to size, center, and zoom map to contain all markers - private fitBounds = (map: google.maps.Map) => { - const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds(); - const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); - !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds())); - }; - - /** - * Custom control for add marker button - * @param controlDiv - * @param map - */ - private CenterControl = () => { - const controlDiv = document.createElement('div'); - controlDiv.className = 'MapBox2-addMarker'; - // Set CSS for the control border. - const controlUI = document.createElement('div'); - controlUI.style.backgroundColor = '#fff'; - controlUI.style.borderRadius = '3px'; - controlUI.style.cursor = 'pointer'; - controlUI.style.marginTop = '10px'; - controlUI.style.borderRadius = '4px'; - controlUI.style.marginBottom = '22px'; - controlUI.style.textAlign = 'center'; - controlUI.style.position = 'absolute'; - controlUI.style.width = '32px'; - controlUI.style.height = '32px'; - controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.'; - - const plIcon = document.createElement('img'); - plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png'; - plIcon.style.color = 'rgb(25,25,25)'; - plIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; - plIcon.style.fontSize = '16px'; - plIcon.style.lineHeight = '32px'; - plIcon.style.left = '18'; - plIcon.style.top = '15'; - plIcon.style.position = 'absolute'; - plIcon.width = 14; - plIcon.height = 14; - plIcon.innerHTML = 'Add'; - controlUI.appendChild(plIcon); - - // Set CSS for the control interior. - const markerIcon = document.createElement('img'); - markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png'; - markerIcon.style.color = 'rgb(25,25,25)'; - markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; - markerIcon.style.fontSize = '16px'; - markerIcon.style.lineHeight = '32px'; - markerIcon.style.left = '-2'; - markerIcon.style.top = '1'; - markerIcon.width = 30; - markerIcon.height = 30; - markerIcon.style.position = 'absolute'; - markerIcon.innerHTML = 'Add'; - controlUI.appendChild(markerIcon); - - // Setup the click event listeners - controlUI.addEventListener('click', () => { - if (this.toggleAddMarker === true) { - this.toggleAddMarker = false; - console.log('add marker button status:' + this.toggleAddMarker); - controlUI.style.backgroundColor = '#fff'; - markerIcon.style.color = 'rgb(25,25,25)'; - } else { - this.toggleAddMarker = true; - console.log('add marker button status:' + this.toggleAddMarker); - controlUI.style.backgroundColor = '#4476f7'; - markerIcon.style.color = 'rgb(255,255,255)'; - } - }); - controlDiv.appendChild(controlUI); - return controlDiv; - }; - - /** - * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list - * @param position - the LatLng position where the marker is placed - * @param map - */ - @action - private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => { - const marker = new google.maps.Marker({ - position: position, - map: map, - }); - map.panTo(position); - const mapMarker = Docs.Create.PushpinDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {}); - this.addDocument(mapMarker, this.annotationKey); - }; - - _loadPending = true; - /** - * store a reference to google map instance - * setup the drawing manager on the top right corner of map - * fit map bounds to contain all markers - * @param map - */ - @action - private loadHandler = (map: google.maps.Map) => { - this._map = map; - this._loadPending = true; - const centerControlDiv = this.CenterControl(); - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(centerControlDiv); - //drawingManager.setMap(map); - // if (navigator.geolocation) { - // navigator.geolocation.getCurrentPosition( - // (position: Position) => { - // const pos = { - // lat: position.coords.latitude, - // lng: position.coords.longitude, - // }; - // this._map.setCenter(pos); - // } - // ); - // } else { - // alert("Your geolocation is not supported by browser.") - // }; - map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5)); - map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng))); - setTimeout(() => { - if (this._loadPending && this._map.getBounds()) { - this._loadPending = false; - this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); - } - }, 250); - // listener to addmarker event - this._map.addListener('click', (e: MouseEvent) => { - if (this.toggleAddMarker === true) { - this.placeMarker((e as any).latLng, map); - } - }); - }; - - @action - centered = () => { - if (this._loadPending && this._map.getBounds()) { - this._loadPending = false; - this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); - } - this.dataDoc.mapLat = this._map.getCenter()?.lat(); - this.dataDoc.mapLng = this._map.getCenter()?.lng(); - }; - - @action - zoomChanged = () => { - if (this._loadPending && this._map.getBounds()) { - this._loadPending = false; - this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); - } - this.dataDoc.map_zoom = this._map.getZoom(); - }; - - /** - * Load and render all map markers - * @param marker - * @param place - */ - @action - private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => { - place[Id] ? (this.markerMap[place[Id]] = marker) : null; - }; - - /** - * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true - * @param e - * @param place - */ - @action - private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => { - // set which place was clicked - this.selectedPlace = place; - place.infoWindowOpen = true; - }; - - /** - * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts - * @param doc - * @param sidebarKey - * @returns - */ - sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { - console.log('print all sidebar Docs'); - if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); - const docs = doc instanceof Doc ? [doc] : doc; - docs.forEach(doc => { - if (doc.lat !== undefined && doc.lng !== undefined) { - const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng); - if (existingMarker) { - Doc.AddDocToList(existingMarker, 'data', doc); - } else { - const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); - this.addDocument(marker, this.annotationKey); - } - } - }); //add to annotation list - - return this.addDocument(doc, sidebarKey); // add to sidebar list - }; - - /** - * Removing documents from the sidebar - * @param doc - * @param sidebarKey - * @returns - */ - sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => { - if (this.layoutDoc._layout_showSidebar) this.toggleSidebar(); - const docs = doc instanceof Doc ? [doc] : doc; - return this.removeDocument(doc, sidebarKey); - }; - - /** - * Toggle sidebar onclick the tiny comment button on the top right corner - * @param e - */ - sidebarBtnDown = (e: React.PointerEvent) => { - setupMoveUpEvents( - this, - e, - (e, down, delta) => - runInAction(() => { - const localDelta = this.props - .ScreenToLocalTransform() - .scale(this.props.NativeDimScaling?.() || 1) - .transformDirection(delta[0], delta[1]); - const fullWidth = NumCast(this.layoutDoc._width); - const mapWidth = fullWidth - this.sidebarWidth(); - if (this.sidebarWidth() + localDelta[0] > 0) { - this._showSidebar = true; - this.layoutDoc._width = fullWidth + localDelta[0]; - this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%'; - } else { - this._showSidebar = false; - this.layoutDoc._width = mapWidth; - this.layoutDoc._layout_sidebarWidthPercent = '0%'; - } - return false; - }), - emptyFunction, - () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map') - ); - }; - - sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); - @computed get layout_sidebarWidthPercent() { - return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); - } - @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); - } - - /** - * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted; - * add a customized temporary marker on the map - */ - @action - private handlePlaceChanged = () => { - const place = this.searchBox.getPlace(); - - if (!place.geometry || !place.geometry.location) { - // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed - window.alert("No details available for input: '" + place.name + "'"); - return; - } - - // zoom in on the location of the search result - if (place.geometry.viewport) { - this._map.fitBounds(place.geometry.viewport); - } else { - this._map.setCenter(place.geometry.location); - this._map.setZoom(17); - } - - // customize icon => customized icon for the nature of the location selected - const icon = { - url: place.icon as string, - size: new google.maps.Size(71, 71), - origin: new google.maps.Point(0, 0), - anchor: new google.maps.Point(17, 34), - scaledSize: new google.maps.Size(25, 25), - }; - - // put temporary cutomized marker on searched location - this.searchMarkers.forEach(marker => { - marker.setMap(null); - }); - this.searchMarkers = []; - this.searchMarkers.push( - new window.google.maps.Marker({ - map: this._map, - icon, - title: place.name, - position: place.geometry.location, - }) - ); - }; - - /** - * Handles toggle of sidebar on click the little comment button - */ - @computed get sidebarHandle() { - return ( - <div - className="MapBox2-overlayButton-sidebar" - key="sidebar" - title="Toggle Sidebar" - style={{ - display: !this.props.isContentActive() ? 'none' : undefined, - top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5, - backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, - }} - onPointerDown={this.sidebarBtnDown}> - <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" /> - </div> - ); - } - - // TODO: Adding highlight box layer to Maps - @action - toggleSidebar = () => { - //1.2 * w * ? = .2 * w .2/1.2 - const prevWidth = this.sidebarWidth(); - this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%'; - this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); - }; - - sidebarDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); - }; - sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { - const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; - this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%'; - e.preventDefault(); - return false; - }; - - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); - - addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { - return this.addDocument(doc, annotationKey); - }; - - pointerEvents = () => { - return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; - }; - @computed get annotationLayer() { - return ( - <div className="MapBox2-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> - {this.inlineTextAnnotations - .sort((a, b) => NumCast(a.y) - NumCast(b.y)) - .map(anno => ( - <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} /> - ))} - </div> - ); - } - - getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.Document; - - /** - * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker - * @returns - */ - private renderMarkers = () => { - return this.allMapMarkers.map(place => ( - <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} /> - )); - }; - - // TODO: auto center on select a document in the sidebar - private handleMapCenter = (map: google.maps.Map) => { - // console.log("print the selected views in selectionManager:") - // if (SelectionManager.Views().lastElement()) { - // console.log(SelectionManager.Views().lastElement()); - // } - }; - - panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); - panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; - infoWidth = () => this.props.PanelWidth() / 5; - infoHeight = () => this.props.PanelHeight() / 5; - anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - savedAnnotations = () => this._savedAnnotations; - - get MicrosoftMaps() { - return (window as any).Microsoft.Maps; - } - render() { - const renderAnnotations = (childFilters?: () => string[]) => null; - return ( - <div className="MapBox2" ref={this._ref}> - <div - className="MapBox2-wrapper" - onWheel={e => e.stopPropagation()} - onPointerDown={async e => { - e.button === 0 && !e.ctrlKey && e.stopPropagation(); - }} - style={{ width: `calc(100% - ${this.layout_sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> - <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div> - {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? null : renderAnnotations()} - {this.annotationLayer} - - <div> - <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}> - <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}> - <input className="MapBox2-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" /> - </Autocomplete> - - {this.renderMarkers()} - {this.allMapMarkers - .filter(marker => marker.infoWindowOpen) - .map(marker => ( - <MapBoxInfoWindow - key={marker[Id]} - {...this.props} - setContentView={emptyFunction} - place={marker} - markerMap={this.markerMap} - PanelWidth={this.infoWidth} - PanelHeight={this.infoHeight} - moveDocument={this.moveDocument} - isAnyChildContentActive={this.isAnyChildContentActive} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - /> - ))} - {/* {this.handleMapCenter(this._map)} */} - </GoogleMap> - </div> - </div> - {/* </LoadScript > */} - <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - <SidebarAnnos - ref={this._sidebarRef} - {...this.props} - fieldKey={this.fieldKey} - Document={this.Document} - layoutDoc={this.layoutDoc} - dataDoc={this.dataDoc} - usePanelWidth={true} - showSidebar={this.SidebarShown} - nativeWidth={NumCast(this.layoutDoc._nativeWidth)} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - PanelWidth={this.sidebarWidth} - sidebarAddDocument={this.sidebarAddDocument} - moveDocument={this.moveDocument} - removeDocument={this.sidebarRemoveDocument} - /> - </div> - {this.sidebarHandle} - </div> - ); - } -} +// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +// import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; +// import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx'; +// import { observer } from 'mobx-react'; +// import * as React from 'react'; +// import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; +// import { Id } from '../../../../fields/FieldSymbols'; +// import { NumCast, StrCast } from '../../../../fields/Types'; +// import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; +// import { Docs } from '../../../documents/Documents'; +// import { DragManager } from '../../../util/DragManager'; +// import { SnappingManager } from '../../../util/SnappingManager'; +// import { UndoManager } from '../../../util/UndoManager'; +// import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; +// import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; +// import { Colors } from '../../global/globalEnums'; +// import { AnchorMenu } from '../../pdf/AnchorMenu'; +// import { Annotation } from '../../pdf/Annotation'; +// import { SidebarAnnos } from '../../SidebarAnnos'; +// import { FieldView, FieldViewProps } from '../FieldView'; +// import { PinProps } from '../trails'; +// import './MapBox2.scss'; +// import { MapBoxInfoWindow } from './MapBoxInfoWindow'; + +// /** +// * MapBox2 architecture: +// * Main component: MapBox2.tsx +// * Supporting Components: SidebarAnnos, CollectionStackingView +// * +// * MapBox2 is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content. +// * The main body of MapBox2 uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view. +// * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available, +// * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map). +// * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts). +// * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps +// */ + +// // const _global = (window /* browser */ || global /* node */) as any; + +// const mapContainerStyle = { +// height: '100%', +// }; + +// const defaultCenter = { +// lat: 42.360081, +// lng: -71.058884, +// }; + +// const mapOptions = { +// fullscreenControl: false, +// }; + +// const apiKey = process.env.GOOGLE_MAPS; + +// const script = document.createElement('script'); +// script.defer = true; +// script.async = true; +// script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`; +// console.log(script.src); +// document.head.appendChild(script); + +// /** +// * Consider integrating later: allows for drawing, circling, making shapes on map +// */ +// // const drawingManager = new window.google.maps.drawing.DrawingManager({ +// // drawingControl: true, +// // drawingControlOptions: { +// // position: google.maps.ControlPosition.TOP_RIGHT, +// // drawingModes: [ +// // google.maps.drawing.OverlayType.MARKER, +// // // currently we are not supporting the following drawing mode on map, a thought for future development +// // google.maps.drawing.OverlayType.CIRCLE, +// // google.maps.drawing.OverlayType.POLYLINE, +// // ], +// // }, +// // }); + +// // options for searchbox in Google Maps Places Autocomplete API +// const options = { +// fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields +// strictBounds: false, +// types: ['establishment'], // type pf places, subject of change according to user need +// } as google.maps.places.AutocompleteOptions; + +// @observer +// export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() { +// private _dropDisposer?: DragManager.DragDropDisposer; +// private _disposers: { [name: string]: IReactionDisposer } = {}; +// private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); +// @observable private _overlayAnnoInfo: Opt<Doc>; +// showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno)); +// public static LayoutString(fieldKey: string) { +// return FieldView.LayoutString(MapBox2, fieldKey); +// } +// public get SidebarKey() { +// return this.fieldKey + '_sidebar'; +// } +// private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void); +// @computed get inlineTextAnnotations() { +// return this.allMapMarkers.filter(a => a.text_inlineAnnotations); +// } + +// @observable private _map: google.maps.Map = null as unknown as google.maps.Map; +// @observable private selectedPlace: Doc | undefined; +// @observable private markerMap: { [id: string]: google.maps.Marker } = {}; +// @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter; +// @observable private inputRef = React.createRef<HTMLInputElement>(); +// @observable private searchMarkers: google.maps.Marker[] = []; +// @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options); +// @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); +// @computed get allSidebarDocs() { +// return DocListCast(this.dataDoc[this.SidebarKey]); +// } +// @computed get allMapMarkers() { +// return DocListCast(this.dataDoc[this.annotationKey]); +// } +// @observable private toggleAddMarker = false; + +// @observable _showSidebar = false; +// @computed get SidebarShown() { +// return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false; +// } + +// static _canAnnotate = true; +// static _hadSelection: boolean = false; +// private _sidebarRef = React.createRef<SidebarAnnos>(); +// private _ref: React.RefObject<HTMLDivElement> = React.createRef(); + +// componentDidMount() { +// this.props.setContentView?.(this); +// } + +// @action +// private setSearchBox = (searchBox: any) => { +// this.searchBox = searchBox; +// }; + +// // iterate allMarkers to size, center, and zoom map to contain all markers +// private fitBounds = (map: google.maps.Map) => { +// const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds(); +// const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); +// !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds())); +// }; + +// /** +// * Custom control for add marker button +// * @param controlDiv +// * @param map +// */ +// private CenterControl = () => { +// const controlDiv = document.createElement('div'); +// controlDiv.className = 'MapBox2-addMarker'; +// // Set CSS for the control border. +// const controlUI = document.createElement('div'); +// controlUI.style.backgroundColor = '#fff'; +// controlUI.style.borderRadius = '3px'; +// controlUI.style.cursor = 'pointer'; +// controlUI.style.marginTop = '10px'; +// controlUI.style.borderRadius = '4px'; +// controlUI.style.marginBottom = '22px'; +// controlUI.style.textAlign = 'center'; +// controlUI.style.position = 'absolute'; +// controlUI.style.width = '32px'; +// controlUI.style.height = '32px'; +// controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.'; + +// const plIcon = document.createElement('img'); +// plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png'; +// plIcon.style.color = 'rgb(25,25,25)'; +// plIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; +// plIcon.style.fontSize = '16px'; +// plIcon.style.lineHeight = '32px'; +// plIcon.style.left = '18'; +// plIcon.style.top = '15'; +// plIcon.style.position = 'absolute'; +// plIcon.width = 14; +// plIcon.height = 14; +// plIcon.innerHTML = 'Add'; +// controlUI.appendChild(plIcon); + +// // Set CSS for the control interior. +// const markerIcon = document.createElement('img'); +// markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png'; +// markerIcon.style.color = 'rgb(25,25,25)'; +// markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; +// markerIcon.style.fontSize = '16px'; +// markerIcon.style.lineHeight = '32px'; +// markerIcon.style.left = '-2'; +// markerIcon.style.top = '1'; +// markerIcon.width = 30; +// markerIcon.height = 30; +// markerIcon.style.position = 'absolute'; +// markerIcon.innerHTML = 'Add'; +// controlUI.appendChild(markerIcon); + +// // Setup the click event listeners +// controlUI.addEventListener('click', () => { +// if (this.toggleAddMarker === true) { +// this.toggleAddMarker = false; +// console.log('add marker button status:' + this.toggleAddMarker); +// controlUI.style.backgroundColor = '#fff'; +// markerIcon.style.color = 'rgb(25,25,25)'; +// } else { +// this.toggleAddMarker = true; +// console.log('add marker button status:' + this.toggleAddMarker); +// controlUI.style.backgroundColor = '#4476f7'; +// markerIcon.style.color = 'rgb(255,255,255)'; +// } +// }); +// controlDiv.appendChild(controlUI); +// return controlDiv; +// }; + +// /** +// * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list +// * @param position - the LatLng position where the marker is placed +// * @param map +// */ +// @action +// private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => { +// const marker = new google.maps.Marker({ +// position: position, +// map: map, +// }); +// map.panTo(position); +// const mapMarker = Docs.Create.PushpinDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {}); +// this.addDocument(mapMarker, this.annotationKey); +// }; + +// _loadPending = true; +// /** +// * store a reference to google map instance +// * setup the drawing manager on the top right corner of map +// * fit map bounds to contain all markers +// * @param map +// */ +// @action +// private loadHandler = (map: google.maps.Map) => { +// this._map = map; +// this._loadPending = true; +// const centerControlDiv = this.CenterControl(); +// map.controls[google.maps.ControlPosition.TOP_RIGHT].push(centerControlDiv); +// //drawingManager.setMap(map); +// // if (navigator.geolocation) { +// // navigator.geolocation.getCurrentPosition( +// // (position: Position) => { +// // const pos = { +// // lat: position.coords.latitude, +// // lng: position.coords.longitude, +// // }; +// // this._map.setCenter(pos); +// // } +// // ); +// // } else { +// // alert("Your geolocation is not supported by browser.") +// // }; +// map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5)); +// map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng))); +// setTimeout(() => { +// if (this._loadPending && this._map.getBounds()) { +// this._loadPending = false; +// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); +// } +// }, 250); +// // listener to addmarker event +// this._map.addListener('click', (e: MouseEvent) => { +// if (this.toggleAddMarker === true) { +// this.placeMarker((e as any).latLng, map); +// } +// }); +// }; + +// @action +// centered = () => { +// if (this._loadPending && this._map.getBounds()) { +// this._loadPending = false; +// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); +// } +// this.dataDoc.mapLat = this._map.getCenter()?.lat(); +// this.dataDoc.mapLng = this._map.getCenter()?.lng(); +// }; + +// @action +// zoomChanged = () => { +// if (this._loadPending && this._map.getBounds()) { +// this._loadPending = false; +// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); +// } +// this.dataDoc.map_zoom = this._map.getZoom(); +// }; + +// /** +// * Load and render all map markers +// * @param marker +// * @param place +// */ +// @action +// private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => { +// place[Id] ? (this.markerMap[place[Id]] = marker) : null; +// }; + +// /** +// * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true +// * @param e +// * @param place +// */ +// @action +// private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => { +// // set which place was clicked +// this.selectedPlace = place; +// place.infoWindowOpen = true; +// }; + +// /** +// * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts +// * @param doc +// * @param sidebarKey +// * @returns +// */ +// sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { +// console.log('print all sidebar Docs'); +// if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); +// const docs = doc instanceof Doc ? [doc] : doc; +// docs.forEach(doc => { +// if (doc.lat !== undefined && doc.lng !== undefined) { +// const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng); +// if (existingMarker) { +// Doc.AddDocToList(existingMarker, 'data', doc); +// } else { +// const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); +// this.addDocument(marker, this.annotationKey); +// } +// } +// }); //add to annotation list + +// return this.addDocument(doc, sidebarKey); // add to sidebar list +// }; + +// /** +// * Removing documents from the sidebar +// * @param doc +// * @param sidebarKey +// * @returns +// */ +// sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => { +// if (this.layoutDoc._layout_showSidebar) this.toggleSidebar(); +// const docs = doc instanceof Doc ? [doc] : doc; +// return this.removeDocument(doc, sidebarKey); +// }; + +// /** +// * Toggle sidebar onclick the tiny comment button on the top right corner +// * @param e +// */ +// sidebarBtnDown = (e: React.PointerEvent) => { +// setupMoveUpEvents( +// this, +// e, +// (e, down, delta) => +// runInAction(() => { +// const localDelta = this.props +// .ScreenToLocalTransform() +// .scale(this.props.NativeDimScaling?.() || 1) +// .transformDirection(delta[0], delta[1]); +// const fullWidth = NumCast(this.layoutDoc._width); +// const mapWidth = fullWidth - this.sidebarWidth(); +// if (this.sidebarWidth() + localDelta[0] > 0) { +// this._showSidebar = true; +// this.layoutDoc._width = fullWidth + localDelta[0]; +// this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%'; +// } else { +// this._showSidebar = false; +// this.layoutDoc._width = mapWidth; +// this.layoutDoc._layout_sidebarWidthPercent = '0%'; +// } +// return false; +// }), +// emptyFunction, +// () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map') +// ); +// }; + +// sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); +// @computed get layout_sidebarWidthPercent() { +// return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); +// } +// @computed get sidebarColor() { +// return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); +// } + +// /** +// * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted; +// * add a customized temporary marker on the map +// */ +// @action +// private handlePlaceChanged = () => { +// const place = this.searchBox.getPlace(); + +// if (!place.geometry || !place.geometry.location) { +// // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed +// window.alert("No details available for input: '" + place.name + "'"); +// return; +// } + +// // zoom in on the location of the search result +// if (place.geometry.viewport) { +// this._map.fitBounds(place.geometry.viewport); +// } else { +// this._map.setCenter(place.geometry.location); +// this._map.setZoom(17); +// } + +// // customize icon => customized icon for the nature of the location selected +// const icon = { +// url: place.icon as string, +// size: new google.maps.Size(71, 71), +// origin: new google.maps.Point(0, 0), +// anchor: new google.maps.Point(17, 34), +// scaledSize: new google.maps.Size(25, 25), +// }; + +// // put temporary cutomized marker on searched location +// this.searchMarkers.forEach(marker => { +// marker.setMap(null); +// }); +// this.searchMarkers = []; +// this.searchMarkers.push( +// new window.google.maps.Marker({ +// map: this._map, +// icon, +// title: place.name, +// position: place.geometry.location, +// }) +// ); +// }; + +// /** +// * Handles toggle of sidebar on click the little comment button +// */ +// @computed get sidebarHandle() { +// return ( +// <div +// className="MapBox2-overlayButton-sidebar" +// key="sidebar" +// title="Toggle Sidebar" +// style={{ +// display: !this.props.isContentActive() ? 'none' : undefined, +// top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5, +// backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, +// }} +// onPointerDown={this.sidebarBtnDown}> +// <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" /> +// </div> +// ); +// } + +// // TODO: Adding highlight box layer to Maps +// @action +// toggleSidebar = () => { +// //1.2 * w * ? = .2 * w .2/1.2 +// const prevWidth = this.sidebarWidth(); +// this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%'; +// this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); +// }; + +// sidebarDown = (e: React.PointerEvent) => { +// setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); +// }; +// sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { +// const bounds = this._ref.current!.getBoundingClientRect(); +// this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; +// this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%'; +// e.preventDefault(); +// return false; +// }; + +// setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); + +// addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { +// return this.addDocument(doc, annotationKey); +// }; + +// pointerEvents = () => { +// return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; +// }; +// @computed get annotationLayer() { +// return ( +// <div className="MapBox2-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> +// {this.inlineTextAnnotations +// .sort((a, b) => NumCast(a.y) - NumCast(b.y)) +// .map(anno => ( +// <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} /> +// ))} +// </div> +// ); +// } + +// getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.Document; + +// /** +// * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker +// * @returns +// */ +// private renderMarkers = () => { +// return this.allMapMarkers.map(place => ( +// <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} /> +// )); +// }; + +// // TODO: auto center on select a document in the sidebar +// private handleMapCenter = (map: google.maps.Map) => { +// // console.log("print the selected views in selectionManager:") +// // if (SelectionManager.Views().lastElement()) { +// // console.log(SelectionManager.Views().lastElement()); +// // } +// }; + +// panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); +// panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); +// scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); +// transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; +// opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; +// infoWidth = () => this.props.PanelWidth() / 5; +// infoHeight = () => this.props.PanelHeight() / 5; +// anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; +// savedAnnotations = () => this._savedAnnotations; + +// get MicrosoftMaps() { +// return (window as any).Microsoft.Maps; +// } +// render() { +// const renderAnnotations = (childFilters?: () => string[]) => null; +// return ( +// <div className="MapBox2" ref={this._ref}> +// <div +// className="MapBox2-wrapper" +// onWheel={e => e.stopPropagation()} +// onPointerDown={async e => { +// e.button === 0 && !e.ctrlKey && e.stopPropagation(); +// }} +// style={{ width: `calc(100% - ${this.layout_sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> +// <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div> +// {renderAnnotations(this.opaqueFilter)} +// {SnappingManager.GetIsDragging() ? null : renderAnnotations()} +// {this.annotationLayer} + +// <div> +// <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}> +// <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}> +// <input className="MapBox2-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" /> +// </Autocomplete> + +// {this.renderMarkers()} +// {this.allMapMarkers +// .filter(marker => marker.infoWindowOpen) +// .map(marker => ( +// <MapBoxInfoWindow +// key={marker[Id]} +// {...this.props} +// setContentView={emptyFunction} +// place={marker} +// markerMap={this.markerMap} +// PanelWidth={this.infoWidth} +// PanelHeight={this.infoHeight} +// moveDocument={this.moveDocument} +// isAnyChildContentActive={this.isAnyChildContentActive} +// whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} +// /> +// ))} +// {/* {this.handleMapCenter(this._map)} */} +// </GoogleMap> +// </div> +// </div> +// {/* </LoadScript > */} +// <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> +// <SidebarAnnos +// ref={this._sidebarRef} +// {...this.props} +// fieldKey={this.fieldKey} +// Document={this.Document} +// layoutDoc={this.layoutDoc} +// dataDoc={this.dataDoc} +// usePanelWidth={true} +// showSidebar={this.SidebarShown} +// nativeWidth={NumCast(this.layoutDoc._nativeWidth)} +// whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} +// PanelWidth={this.sidebarWidth} +// sidebarAddDocument={this.sidebarAddDocument} +// moveDocument={this.moveDocument} +// removeDocument={this.sidebarRemoveDocument} +// /> +// </div> +// {this.sidebarHandle} +// </div> +// ); +// } +// } diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index 415a9d776..a9c6ba22c 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -1,95 +1,95 @@ -import { InfoWindow } from '@react-google-maps/api'; -import { action } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc } from '../../../../fields/Doc'; -import { Id } from '../../../../fields/FieldSymbols'; -import { emptyFunction, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils'; -import { Docs } from '../../../documents/Documents'; -import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; -import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView'; -import { CollectionStackingView } from '../../collections/CollectionStackingView'; -import { ViewBoxAnnotatableProps } from '../../DocComponent'; -import { FieldViewProps } from '../FieldView'; -import { FormattedTextBox } from '../formattedText/FormattedTextBox'; -import './MapBox.scss'; +// import { InfoWindow } from '@react-google-maps/api'; +// import { action } from 'mobx'; +// import { observer } from 'mobx-react'; +// import * as React from 'react'; +// import { Doc } from '../../../../fields/Doc'; +// import { Id } from '../../../../fields/FieldSymbols'; +// import { emptyFunction, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils'; +// import { Docs } from '../../../documents/Documents'; +// import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +// import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView'; +// import { CollectionStackingView } from '../../collections/CollectionStackingView'; +// import { ViewBoxAnnotatableProps } from '../../DocComponent'; +// import { FieldViewProps } from '../FieldView'; +// import { FormattedTextBox } from '../formattedText/FormattedTextBox'; +// import './MapBox.scss'; -interface MapBoxInfoWindowProps { - place: Doc; - renderDepth: number; - markerMap: { [id: string]: google.maps.Marker }; - isAnyChildContentActive: () => boolean; -} -@observer -export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> { - @action - private handleInfoWindowClose = () => { - if (this.props.place.infoWindowOpen) { - this.props.place.infoWindowOpen = false; - } - this.props.place.infoWindowOpen = false; - }; +// interface MapBoxInfoWindowProps { +// place: Doc; +// renderDepth: number; +// markerMap: { [id: string]: google.maps.Marker }; +// isAnyChildContentActive: () => boolean; +// } +// @observer +// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> { +// @action +// private handleInfoWindowClose = () => { +// if (this.props.place.infoWindowOpen) { +// this.props.place.infoWindowOpen = false; +// } +// this.props.place.infoWindowOpen = false; +// }; - addNoteClick = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => { - const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true }); - FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed - Doc.AddDocToList(this.props.place, 'data', newDoc); - this._stack?.scrollToBottom(); - e.stopPropagation(); - e.preventDefault(); - }); - }; +// addNoteClick = (e: React.PointerEvent) => { +// setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => { +// const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true }); +// FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed +// Doc.AddDocToList(this.props.place, 'data', newDoc); +// this._stack?.scrollToBottom(); +// e.stopPropagation(); +// e.preventDefault(); +// }); +// }; - _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined; - childLayoutFitWidth = (doc: Doc) => doc.type === DocumentType.RTF; - addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean); - removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean); - render() { - return ( - <InfoWindow - // anchor={this.props.markerMap[this.props.place[Id]]} - onCloseClick={this.handleInfoWindowClose}> - <div className="mapbox-infowindow"> - <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}> - <CollectionStackingView - ref={r => (this._stack = r)} - {...this.props} - setContentView={emptyFunction} - Document={this.props.place} - TemplateDataDocument={undefined} - fieldKey="data" - NativeWidth={returnZero} - NativeHeight={returnZero} - childFilters={returnEmptyFilter} - setHeight={emptyFunction} - isAnnotationOverlay={false} - select={emptyFunction} - NativeDimScaling={returnOne} - isContentActive={returnTrue} - chromeHidden={true} - childHideResizeHandles={true} - childHideDecorationTitle={true} - childLayoutFitWidth={this.childLayoutFitWidth} - // childDocumentsActive={returnFalse} - removeDocument={this.removeDoc} - addDocument={this.addDoc} - renderDepth={this.props.renderDepth + 1} - type_collection={CollectionViewType.Stacking} - pointerEvents={returnAll} - /> - </div> - <hr /> - <div - onPointerDown={this.addNoteClick} - onClick={e => { - e.stopPropagation(); - e.preventDefault(); - }}> - Add Note - </div> - </div> - </InfoWindow> - ); - } -} +// _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined; +// childLayoutFitWidth = (doc: Doc) => doc.type === DocumentType.RTF; +// addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean); +// removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean); +// render() { +// return ( +// <InfoWindow +// // anchor={this.props.markerMap[this.props.place[Id]]} +// onCloseClick={this.handleInfoWindowClose}> +// <div className="mapbox-infowindow"> +// <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}> +// <CollectionStackingView +// ref={r => (this._stack = r)} +// {...this.props} +// setContentView={emptyFunction} +// Document={this.props.place} +// TemplateDataDocument={undefined} +// fieldKey="data" +// NativeWidth={returnZero} +// NativeHeight={returnZero} +// childFilters={returnEmptyFilter} +// setHeight={emptyFunction} +// isAnnotationOverlay={false} +// select={emptyFunction} +// NativeDimScaling={returnOne} +// isContentActive={returnTrue} +// chromeHidden={true} +// childHideResizeHandles={true} +// childHideDecorationTitle={true} +// childLayoutFitWidth={this.childLayoutFitWidth} +// // childDocumentsActive={returnFalse} +// removeDocument={this.removeDoc} +// addDocument={this.addDoc} +// renderDepth={this.props.renderDepth + 1} +// type_collection={CollectionViewType.Stacking} +// pointerEvents={returnAll} +// /> +// </div> +// <hr /> +// <div +// onPointerDown={this.addNoteClick} +// onClick={e => { +// e.stopPropagation(); +// e.preventDefault(); +// }}> +// Add Note +// </div> +// </div> +// </InfoWindow> +// ); +// } +// } diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx index 56f0a49b8..590f8735c 100644 --- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx +++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx @@ -2,7 +2,7 @@ import { observer } from 'mobx-react'; // import { SettingsManager } from '../../../util/SettingsManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; -import React = require('react'); +import * as React from 'react'; import { computed } from 'mobx'; import { MapBox } from './MapBox'; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 9787de0ab..8de8498d7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -31,7 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; import { PinProps, PresBox } from './trails'; -import React = require('react'); +import * as React from 'react'; @observer export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index cd1ff17dd..c498a58d6 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -15,11 +15,11 @@ import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from './../FieldView'; import './PhysicsSimulationBox.scss'; import InputField from './PhysicsSimulationInputField'; -import * as questions from './PhysicsSimulationQuestions.json'; -import * as tutorials from './PhysicsSimulationTutorial.json'; +import questions from './PhysicsSimulationQuestions.json'; +import tutorials from './PhysicsSimulationTutorial.json'; import Wall from './PhysicsSimulationWall'; import Weight from './PhysicsSimulationWeight'; -import React = require('react'); +import * as React from 'react'; interface IWallProps { length: number; diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx index d595a499e..c704863f5 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx @@ -1,6 +1,6 @@ import { TextField, InputAdornment } from '@mui/material'; import { Doc } from '../../../../fields/Doc'; -import React = require('react'); +import * as React from 'react'; import TaskAltIcon from '@mui/icons-material/TaskAlt'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import { isNumber } from 'lodash'; diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx index 8cc1d0fbf..696352296 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx @@ -1,34 +1,33 @@ -import React = require('react'); +import * as React from 'react'; export interface Force { - magnitude: number; - directionInDegrees: number; + magnitude: number; + directionInDegrees: number; } export interface IWallProps { - length: number; - xPos: number; - yPos: number; - angleInDegrees: number; + length: number; + xPos: number; + yPos: number; + angleInDegrees: number; } export default class Wall extends React.Component<IWallProps> { + constructor(props: any) { + super(props); + } - constructor(props: any) { - super(props) - } + wallStyle = { + width: this.props.angleInDegrees == 0 ? this.props.length + '%' : '5px', + height: this.props.angleInDegrees == 0 ? '5px' : this.props.length + '%', + position: 'absolute' as 'absolute', + left: this.props.xPos + '%', + top: this.props.yPos + '%', + backgroundColor: '#6c7b8b', + margin: 0, + padding: 0, + }; - wallStyle = { - width: this.props.angleInDegrees == 0 ? this.props.length + "%" : "5px", - height: this.props.angleInDegrees == 0 ? "5px" : this.props.length + "%", - position: "absolute" as "absolute", - left: this.props.xPos + "%", - top: this.props.yPos + "%", - backgroundColor: "#6c7b8b", - margin: 0, - padding: 0, - }; - - render () { - return (<div style={this.wallStyle}></div>); - } -}; + render() { + return <div style={this.wallStyle}></div>; + } +} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx index 2165c8ba9..99333991f 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -1,7 +1,7 @@ import { computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import './PhysicsSimulationBox.scss'; -import React = require('react'); +import * as React from 'react'; interface IWallProps { length: number; diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx index 7f0956e51..191877cb5 100644 --- a/src/client/views/nodes/RadialMenu.tsx +++ b/src/client/views/nodes/RadialMenu.tsx @@ -1,8 +1,8 @@ -import React = require("react"); -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import "./RadialMenu.scss"; -import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem"; +import * as React from 'react'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import './RadialMenu.scss'; +import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem'; @observer export class RadialMenu extends React.Component { @@ -23,11 +23,10 @@ export class RadialMenu extends React.Component { public used: boolean = false; - catchTouch = (te: React.TouchEvent) => { te.stopPropagation(); te.preventDefault(); - } + }; @action onPointerDown = (e: PointerEvent) => { @@ -35,8 +34,8 @@ export class RadialMenu extends React.Component { this._mouseX = e.clientX; this._mouseY = e.clientY; this.used = false; - document.addEventListener("pointermove", this.onPointerMove); - } + document.addEventListener('pointermove', this.onPointerMove); + }; @observable private _closest: number = -1; @@ -60,11 +59,10 @@ export class RadialMenu extends React.Component { } } this._closest = closest; - } - else { + } else { this._closest = -1; } - } + }; @action onPointerUp = (e: PointerEvent) => { this.used = true; @@ -75,43 +73,41 @@ export class RadialMenu extends React.Component { this._shouldDisplay = false; } this._shouldDisplay && (this._display = true); - document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener('pointermove', this.onPointerMove); if (this._closest !== -1 && this._items?.length > this._closest) { this._items[this._closest].event(); } - } + }; componentWillUnmount() { - document.removeEventListener("pointerdown", this.onPointerDown); + document.removeEventListener('pointerdown', this.onPointerDown); - document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener('pointerup', this.onPointerUp); this._reactionDisposer && this._reactionDisposer(); } @action componentDidMount = () => { - document.addEventListener("pointerdown", this.onPointerDown); - document.addEventListener("pointerup", this.onPointerUp); + document.addEventListener('pointerdown', this.onPointerDown); + document.addEventListener('pointerup', this.onPointerUp); this.previewcircle(); this._reactionDisposer = reaction( () => this._shouldDisplay, - () => this._shouldDisplay && !this._mouseDown && runInAction(() => this._display = true) + () => this._shouldDisplay && !this._mouseDown && runInAction(() => (this._display = true)) ); - } + }; componentDidUpdate = () => { this.previewcircle(); - } + }; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable _display: boolean = false; @observable private _yRelativeToTop: boolean = true; - @observable private _width: number = 0; @observable private _height: number = 0; - getItems() { return this._items; } @@ -133,7 +129,7 @@ export class RadialMenu extends React.Component { this._mouseX = x; this._mouseY = y; this._shouldDisplay = true; - } + }; // @computed // get pageX() { // const x = this._pageX; @@ -168,7 +164,7 @@ export class RadialMenu extends React.Component { this.clearItems(); this._display = false; this._shouldDisplay = false; - } + }; @action openMenu = (x: number, y: number) => { @@ -176,56 +172,52 @@ export class RadialMenu extends React.Component { this._pageY = y; this._shouldDisplay; this._display = true; - } + }; @action clearItems() { this._items = []; } - previewcircle() { - if (document.getElementById("newCanvas") !== null) { - const c: any = document.getElementById("newCanvas"); + if (document.getElementById('newCanvas') !== null) { + const c: any = document.getElementById('newCanvas'); if (c.getContext) { - const ctx = c.getContext("2d"); + const ctx = c.getContext('2d'); ctx.beginPath(); ctx.arc(150, 150, 50, 0, 2 * Math.PI); - ctx.fillStyle = "white"; + ctx.fillStyle = 'white'; ctx.fill(); - ctx.font = "12px Arial"; - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - let description = ""; + ctx.font = '12px Arial'; + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + let description = ''; if (this._closest !== -1) { description = this._items[this._closest].description; } if (description.length > 15) { description = description.slice(0, 12); - description += "..."; + description += '...'; } ctx.fillText(description, 150, 150, 90); } } } - render() { if (!this._display) { return null; } - const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : - { left: this._pageX - 130, top: this._pageY - 130 }; + const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : { left: this._pageX - 130, top: this._pageY - 130 }; return ( - <div className="radialMenu-cont" onTouchStart={this.catchTouch} style={style}> - <canvas id="newCanvas" style={{ position: "absolute" }} height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas> + <canvas id="newCanvas" style={{ position: 'absolute' }} height="300" width="300"> + {' '} + Your browser does not support the HTML5 canvas tag. + </canvas> {this.menuItems} </div> - ); } - - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx index 8876b4879..c931202f1 100644 --- a/src/client/views/nodes/RadialMenuItem.tsx +++ b/src/client/views/nodes/RadialMenuItem.tsx @@ -1,8 +1,8 @@ -import React = require("react"); +import * as React from 'react'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { observer } from "mobx-react"; -import { UndoManager } from "../../util/UndoManager"; +import { observer } from 'mobx-react'; +import { UndoManager } from '../../util/UndoManager'; export interface RadialMenuProps { description: string; @@ -15,17 +15,15 @@ export interface RadialMenuProps { selected: number; } - @observer export class RadialMenuItem extends React.Component<RadialMenuProps> { - componentDidMount = () => { this.setcircle(); - } + }; componentDidUpdate = () => { this.setcircle(); - } + }; handleEvent = async (e: React.PointerEvent) => { this.props.closeMenu && this.props.closeMenu(); @@ -35,38 +33,36 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { } await this.props.event({ x: e.clientX, y: e.clientY }); batch && batch.end(); - } - + }; setcircle() { let circlemin = 0; let circlemax = 1; - this.props.min ? circlemin = this.props.min : null; - this.props.max ? circlemax = this.props.max : null; - if (document.getElementById("myCanvas") !== null) { - const c: any = document.getElementById("myCanvas"); - let color = "white"; + this.props.min ? (circlemin = this.props.min) : null; + this.props.max ? (circlemax = this.props.max) : null; + if (document.getElementById('myCanvas') !== null) { + const c: any = document.getElementById('myCanvas'); + let color = 'white'; switch (circlemin % 3) { case 1: - color = "#c2c2c5"; + color = '#c2c2c5'; break; case 0: - color = "#f1efeb"; + color = '#f1efeb'; break; case 2: - color = "lightgray"; + color = 'lightgray'; break; } if (circlemax % 3 === 1 && circlemin === circlemax - 1) { - color = "#c2c2c5"; + color = '#c2c2c5'; } if (this.props.selected === this.props.min) { - color = "#808080"; - + color = '#808080'; } if (c.getContext) { - const ctx = c.getContext("2d"); + const ctx = c.getContext('2d'); ctx.beginPath(); ctx.arc(150, 150, 150, (circlemin / circlemax) * 2 * Math.PI, ((circlemin + 1) / circlemax) * 2 * Math.PI); ctx.arc(150, 150, 50, ((circlemin + 1) / circlemax) * 2 * Math.PI, (circlemin / circlemax) * 2 * Math.PI, true); @@ -79,35 +75,36 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { calculatorx() { let circlemin = 0; let circlemax = 1; - this.props.min ? circlemin = this.props.min : null; - this.props.max ? circlemax = this.props.max : null; - const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; + this.props.min ? (circlemin = this.props.min) : null; + this.props.max ? (circlemax = this.props.max) : null; + const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2; const degrees = 360 * avg; - const x = 100 * Math.cos(degrees * Math.PI / 180); - const y = -125 * Math.sin(degrees * Math.PI / 180); + const x = 100 * Math.cos((degrees * Math.PI) / 180); + const y = -125 * Math.sin((degrees * Math.PI) / 180); return x; } calculatory() { - let circlemin = 0; let circlemax = 1; - this.props.min ? circlemin = this.props.min : null; - this.props.max ? circlemax = this.props.max : null; - const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; + this.props.min ? (circlemin = this.props.min) : null; + this.props.max ? (circlemax = this.props.max) : null; + const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2; const degrees = 360 * avg; - const x = 125 * Math.cos(degrees * Math.PI / 180); - const y = -100 * Math.sin(degrees * Math.PI / 180); + const x = 125 * Math.cos((degrees * Math.PI) / 180); + const y = -100 * Math.sin((degrees * Math.PI) / 180); return y; } - render() { return ( - <div className={"radialMenu-item" + (this.props.selected ? " radialMenu-itemSelected" : "")} onPointerUp={this.handleEvent}> - <canvas id="myCanvas" height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas> - <FontAwesomeIcon icon={this.props.icon} size="3x" style={{ position: "absolute", left: this.calculatorx() + 150 - 19, top: this.calculatory() + 150 - 19 }} /> + <div className={'radialMenu-item' + (this.props.selected ? ' radialMenu-itemSelected' : '')} onPointerUp={this.handleEvent}> + <canvas id="myCanvas" height="300" width="300"> + {' '} + Your browser does not support the HTML5 canvas tag. + </canvas> + <FontAwesomeIcon icon={this.props.icon} size="3x" style={{ position: 'absolute', left: this.calculatorx() + 150 - 19, top: this.calculatory() + 150 - 19 }} /> </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index c2e204eab..7c21b1893 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; // import { Canvas } from '@react-three/fiber'; import { computed, observable, runInAction } from 'mobx'; diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss index 8d76f2b1d..9789da55a 100644 --- a/src/client/views/nodes/ScriptingBox.scss +++ b/src/client/views/nodes/ScriptingBox.scss @@ -32,7 +32,7 @@ .scriptingBox-wrapper { width: 100%; height: 100%; - max-height: calc(100%-30px); + max-height: calc(100% - 30px); display: flex; flex-direction: row; overflow: auto; @@ -42,7 +42,8 @@ overflow: hidden; } - .scriptingBox-textArea, .scriptingBox-textArea-inputs { + .scriptingBox-textArea, + .scriptingBox-textArea-inputs { flex: 70; height: 100%; max-width: 95%; @@ -110,7 +111,7 @@ cursor: pointer; } - .rta__entity>* { + .rta__entity > * { padding-left: 4px; padding-right: 4px; } @@ -125,7 +126,7 @@ .scriptingBox-textArea-inputs { max-width: 100%; height: 40%; - width: 100%; + width: 100%; resize: none; } .scriptingBox-textArea-script { @@ -194,8 +195,8 @@ .scriptingBox-errorMessage { overflow: auto; - background: "red"; - background-color: "red"; + background: 'red'; + background-color: 'red'; height: 45px; } @@ -221,4 +222,4 @@ width: 25%; } } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx index 2a3dd8d2d..9aab8c5a9 100644 --- a/src/client/views/nodes/TaskCompletedBox.tsx +++ b/src/client/views/nodes/TaskCompletedBox.tsx @@ -1,13 +1,11 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import "./TaskCompletedBox.scss"; -import { observable, action } from "mobx"; -import { Fade } from "@material-ui/core"; - +import * as React from 'react'; +import { observer } from 'mobx-react'; +import './TaskCompletedBox.scss'; +import { observable, action } from 'mobx'; +import { Fade } from '@mui/material'; @observer export class TaskCompletionBox extends React.Component<{}> { - @observable public static taskCompleted: boolean = false; @observable public static popupX: number = 500; @observable public static popupY: number = 150; @@ -16,15 +14,20 @@ export class TaskCompletionBox extends React.Component<{}> { @action public static toggleTaskCompleted = () => { TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted; - } + }; render() { - return <Fade in={TaskCompletionBox.taskCompleted}> - <div className="taskCompletedBox-fade" - style={{ - left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500, - top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150, - }}>{TaskCompletionBox.textDisplayed}</div> - </Fade>; + return ( + <Fade in={TaskCompletionBox.taskCompleted}> + <div + className="taskCompletedBox-fade" + style={{ + left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500, + top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150, + }}> + {TaskCompletionBox.textDisplayed} + </div> + </Fade> + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 0f6c6724a..04780155d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index b3afe6ba0..d02e5f3e8 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -36,7 +36,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from './LinkDocPreview'; import { PinProps, PresBox } from './trails'; import './WebBox.scss'; -import React = require('react'); +import * as React from 'react'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index aa269d8d6..d6b053c8e 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -2,7 +2,7 @@ import { TextSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; import { DocServer } from '../../../DocServer'; -import React = require('react'); +import * as React from 'react'; // creates an inline comment in a note when '>>' is typed. // the comment sits on the right side of the note and vertically aligns with its anchor in the text. diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 95743a036..4384c8958 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -11,7 +11,7 @@ import { Docs, DocUtils } from '../../../documents/Documents'; import { Transform } from '../../../util/Transform'; import { DocFocusOptions, DocumentView } from '../DocumentView'; import { FormattedTextBox } from './FormattedTextBox'; -import React = require('react'); +import * as React from 'react'; export class DashDocView { dom: HTMLSpanElement; // container for label and value diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index a914084f9..a395296d0 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; @@ -16,7 +16,7 @@ import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableC import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; -import React = require('react'); +import * as React from 'react'; import { Transform } from '../../../util/Transform'; export class DashFieldView { diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx new file mode 100644 index 000000000..bde6c1315 --- /dev/null +++ b/src/client/views/nodes/formattedText/EquationEditor.tsx @@ -0,0 +1,87 @@ +import React, { Component, createRef } from 'react'; + +// Import JQuery, required for the functioning of the equation editor +import $ from 'jquery'; + +// Import the styles from the Mathquill editor +import 'mathquill/build/mathquill.css'; + +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore +window.jQuery = $; + +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore +require('mathquill/build/mathquill'); + +type EquationEditorProps = { + onChange(latex: string): void; + value: string; + spaceBehavesLikeTab?: boolean; + autoCommands: string; + autoOperatorNames: string; + onEnter?(): void; +}; + +/** + * @typedef {EquationEditorProps} props + * @prop {Function} onChange Triggered when content of the equation editor changes + * @prop {string} value Content of the equation handler + * @prop {boolean}[false] spaceBehavesLikeTab Whether spacebar should simulate tab behavior + * @prop {string} autoCommands List of commands for which you only have to type the name of the + * command with a \ in front of it. Examples: pi theta rho sum + * @prop {string} autoOperatorNames List of operators for which you only have to type the name of the + * operator with a \ in front of it. Examples: sin cos tan + * @prop {Function} onEnter Triggered when enter is pressed in the equation editor + * @extends {Component<EquationEditorProps>} + */ +class EquationEditor extends Component<EquationEditorProps> { + element: any; + mathField: any; + ignoreEditEvents: number; + + // Element needs to be in the class format and thus requires a constructor. The steps that are run + // in the constructor is to make sure that React can succesfully communicate with the equation + // editor. + constructor(props: EquationEditorProps) { + super(props); + + this.element = createRef(); + this.mathField = null; + + // MathJax apparently fire 2 edit events on startup. + this.ignoreEditEvents = 2; + } + + componentDidMount() { + const { onChange, value, spaceBehavesLikeTab, autoCommands, autoOperatorNames, onEnter } = this.props; + + const config = { + handlers: { + edit: () => { + if (this.ignoreEditEvents > 0) { + this.ignoreEditEvents -= 1; + return; + } + if (this.mathField.latex() !== value) { + onChange(this.mathField.latex()); + } + }, + enter: onEnter, + }, + spaceBehavesLikeTab, + autoCommands, + autoOperatorNames, + }; + + // @ts-ignore + this.mathField = (MathQuill as any).MathField(this.element.current, config); + this.mathField.latex(value || ''); + } + + render() { + return <span ref={this.element} style={{ border: '0px', boxShadow: 'None' }} />; + } +} + +export default EquationEditor; diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 5e62d94c2..5ee5d25c3 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -1,4 +1,4 @@ -import EquationEditor from 'equation-editor-react'; +import EquationEditor from './EquationEditor'; import { IReactionDisposer, trace } from 'mobx'; import { observer } from 'mobx-react'; import { TextSelection } from 'prosemirror-state'; @@ -7,7 +7,7 @@ import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; -import React = require('react'); +import * as React from 'react'; export class EquationView { dom: HTMLDivElement; // container for label and value @@ -101,7 +101,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal> <EquationEditor ref={this._ref} value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')} - onChange={str => (this._textBoxDoc[this._fieldKey] = str)} + onChange={(str: any) => (this._textBoxDoc[this._fieldKey] = str)} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" spaceBehavesLikeTab={true} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 42e8ace6e..4f8e8769a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,7 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { setCORS } from 'google-translate-api-browser'; +import { Tooltip } from '@mui/material'; import { isEqual } from 'lodash'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -71,10 +70,8 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; -import applyDevTools = require('prosemirror-dev-tools'); -import React = require('react'); -// setting up cors-anywhere server address -const translate = setCORS('http://cors-anywhere.herokuapp.com/'); +// import * as applyDevTools from 'prosemirror-dev-tools'; +import * as React from 'react'; export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @@ -797,7 +794,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ?.trim() .split(' ') .filter(h => h); - const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0]; + const anchorDoc = Array.from(hrefs ?? []) + .lastElement() + .replace(Doc.localServerPath(), '') + .split('?')[0]; const deleteMarkups = undoBatch(() => { const sel = editor.state.selection; editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor)); @@ -1308,7 +1308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (this._editorView && reference) { const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); - response && (this.dataDoc[GoogleRef] = response.documentId); + response?.documentId && (this.dataDoc[GoogleRef] = response.documentId); const pushSuccess = response !== undefined && !('errors' in response); dataDoc.googleDocUnchanged = pushSuccess; DocumentButtonBar.Instance.startPushOutcome(pushSuccess); @@ -1783,23 +1783,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const state = this._editorView!.state; const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); - if (this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { - try { - translate(curText, { from: 'en', to: 'es' }).then((result1: any) => { - setTimeout( - () => - translate(result1.text, { from: 'es', to: 'en' }).then((result: any) => { - const tb = this._sidebarTagRef.current as FormattedTextBox; - tb._editorView?.dispatch(tb._editorView!.state.tr.insertText(result1.text + '\r\n\r\n' + result.text)); - }), - 1000 - ); - }); - } catch (e: any) { - console.log(e.message); - } - this._lastText = curText; - } if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) { UndoManager.RunInBatch(() => { this.dataDoc.title_custom = true; @@ -1954,7 +1937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } @computed get sidebarCollection() { const renderComponent = (tag: string) => { - const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; + const ComponentTag: any = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; return ComponentTag === CollectionStackingView ? ( <SidebarAnnos ref={this._sidebarRef} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 791a1bfe7..7ce06cf7f 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,6 +1,6 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { lift, wrapIn } from 'prosemirror-commands'; diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 3355e4529..7ec296ed2 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -1,7 +1,7 @@ import { TextSelection } from 'prosemirror-state'; import { Fragment, Node, Slice } from 'prosemirror-model'; import * as ReactDOM from 'react-dom/client'; -import React = require('react'); +import * as React from 'react'; // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked. // this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 4faef26e2..a342285b0 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model'; import { Doc } from '../../../../fields/Doc'; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 4cd2cee52..d023020e1 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model'; import { listItem, orderedList } from 'prosemirror-schema-list'; import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec'; diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 2b0927148..87e1b69c3 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -21,7 +21,7 @@ import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollection import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; -import React = require('react'); +import * as React from 'react'; enum BrushStyle { ADD, @@ -438,7 +438,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // disable once edited has been clicked (doesn't make sense to change after first edit) disabled={edited} checked={isNewCollection} - onChange={e => { + onChange={() => { setIsNewCollection(prev => !prev); }} /> @@ -513,7 +513,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD defaultValue={150} size="small" valueLabelDisplay="auto" - onChange={(e, val) => { + onChange={(e: any, val: any) => { setCursorData(prev => ({ ...prev, width: val as number })); }} /> @@ -571,7 +571,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD <div> <TextField value={input} - onChange={e => setInput(e.target.value)} + onChange={(e: any) => setInput(e.target.value)} disabled={isBrushing} type="text" label="Prompt" diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx index 10eca358e..185ba2280 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -1,5 +1,5 @@ import './GenerativeFillButtons.scss'; -import React = require('react'); +import * as React from 'react'; import ReactLoading from 'react-loading'; import { activeColor } from './generativeFillUtils/generativeFillConstants'; import { Button, IconButton, Type } from 'browndash-components'; diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx index c4c89d8b1..9dc0c5180 100644 --- a/src/client/views/nodes/importBox/ImportElementBox.tsx +++ b/src/client/views/nodes/importBox/ImportElementBox.tsx @@ -7,7 +7,7 @@ import { ViewBoxBaseComponent } from '../../DocComponent'; import { DefaultStyleProvider } from '../../StyleProvider'; import { DocumentView, DocumentViewInternal } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import React = require('react'); +import * as React from 'react'; @observer export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 2303e4126..1317ca814 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,6 +1,6 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index f8aebd021..cd6beac57 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; @@ -24,7 +24,7 @@ import { StyleProp } from '../../StyleProvider'; import { PresBox } from './PresBox'; import './PresElementBox.scss'; import { PresMovement } from './PresEnums'; -import React = require('react'); +import * as React from 'react'; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d68859e88..2d8e9e2a2 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -1,9 +1,9 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { ColorState } from 'react-color'; +import { ColorResult } from 'react-color'; import { Doc, Opt } from '../../../fields/Doc'; import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils'; import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT'; @@ -139,13 +139,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { } @action changeHighlightColor = (color: string) => { - const col: ColorState = { + const col: ColorResult = { hex: color, - hsl: { a: 0, h: 0, s: 0, l: 0, source: '' }, - hsv: { a: 0, h: 0, s: 0, v: 0, source: '' }, - rgb: { a: 0, r: 0, b: 0, g: 0, source: '' }, - oldHue: 0, - source: '', + hsl: { a: 0, h: 0, s: 0, l: 0 }, + rgb: { a: 0, r: 0, b: 0, g: 0 }, }; this.highlightColor = Utils.colorString(col); }; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index e1e87763c..cf19ff6bc 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 038c45582..0cda36509 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -1,10 +1,10 @@ -import React = require('react'); +import * as React from 'react'; import './GPTPopup.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import ReactLoading from 'react-loading'; -import Typist from 'react-typist'; +import { TypeAnimation } from 'react-type-animation'; import { Doc } from '../../../../fields/Doc'; import { DocUtils, Docs } from '../../../documents/Documents'; import { Button, IconButton, Type } from 'browndash-components'; @@ -218,17 +218,18 @@ export class GPTPopup extends React.Component<GPTPopupProps> { <div className="content-wrapper"> {!this.loading && (!this.done ? ( - <Typist - key={this.text} - avgTypingDelay={15} - cursor={{ hideWhenDone: true }} - onTypingDone={() => { - setTimeout(() => { - this.setDone(true); - }, 500); - }}> - {this.text} - </Typist> + <TypeAnimation + speed={50} + sequence={[ + this.text, + () => { + setTimeout(() => { + this.setDone(true); + }, 500); + }, + ]} + //cursor={{ hideWhenDone: true }} + /> ) : ( this.text ))} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 33a336de3..b2373b190 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -23,7 +23,7 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import './PDFViewer.scss'; -import React = require('react'); +import * as React from 'react'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx index 6cf3a5f72..e12e4511c 100644 --- a/src/client/views/search/IconButton.tsx +++ b/src/client/views/search/IconButton.tsx @@ -1,29 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import * as _ from "lodash"; +import * as _ from 'lodash'; import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentType } from '../../documents/DocumentTypes'; import '../global/globalCssVariables.scss'; import { IconBar } from './IconBar'; -import "./IconButton.scss"; -import "./SearchBox.scss"; -import { Font } from '@react-pdf/renderer'; +import './IconButton.scss'; +import './SearchBox.scss'; -interface IconButtonProps { +interface iconButtonProps { type: string; } @observer -export class IconButton extends React.Component<IconButtonProps>{ - +export class IconButton extends React.Component<iconButtonProps> { @observable private _isSelected: boolean = IconBar.Instance.getIcons().indexOf(this.props.type) !== -1; @observable private _hover = false; private _resetReaction?: IReactionDisposer; private _selectAllReaction?: IReactionDisposer; static Instance: IconButton; - constructor(props: IconButtonProps) { + constructor(props: iconButtonProps) { super(props); IconButton.Instance = this; } @@ -40,7 +38,7 @@ export class IconButton extends React.Component<IconButtonProps>{ IconBar.Instance._resetClicked = false; } } - }), + }) ); this._selectAllReaction = reaction( @@ -54,25 +52,25 @@ export class IconButton extends React.Component<IconButtonProps>{ IconBar.Instance._selectAllClicked = false; } } - }), + }) ); - } + }; @action.bound getIcon() { switch (this.props.type) { - case (DocumentType.NONE): return "ban"; - case (DocumentType.AUDIO): return "music"; - case (DocumentType.COL): return "object-group"; - case (DocumentType.IMG): return "image"; - case (DocumentType.LINK): return "link"; - case (DocumentType.PDF): return "file-pdf"; - case (DocumentType.RTF): return "sticky-note"; - case (DocumentType.VID): return "video"; - case (DocumentType.WEB): return "globe-asia"; - case (DocumentType.MAP): return "map-marker-alt"; - default: return "caret-down"; - } + case DocumentType.NONE: return 'ban'; + case DocumentType.AUDIO: return 'music'; + case DocumentType.COL: return 'object-group'; + case DocumentType.IMG: return 'image'; + case DocumentType.LINK: return 'link'; + case DocumentType.PDF: return 'file-pdf'; + case DocumentType.RTF: return 'sticky-note'; + case DocumentType.VID: return 'video'; + case DocumentType.WEB: return 'globe-asia'; + case DocumentType.MAP: return 'map-marker-alt'; + default: return 'caret-down'; + } // prettier-ignore } @action.bound @@ -82,45 +80,39 @@ export class IconButton extends React.Component<IconButtonProps>{ if (!this._isSelected) { this._isSelected = true; newList.push(this.props.type); - } - else { + } else { this._isSelected = false; _.pull(newList, this.props.type); } IconBar.Instance.updateIcon(newList); - } + }; selected = { opacity: 1, - backgroundColor: "#121721", + backgroundColor: '#121721', //backgroundColor: "rgb(128, 128, 128)" }; notSelected = { opacity: 0.2, - backgroundColor: "#121721", + backgroundColor: '#121721', }; hoverStyle = { opacity: 1, - backgroundColor: "rgb(128, 128, 128)" + backgroundColor: 'rgb(128, 128, 128)', //backgroundColor: "rgb(178, 206, 248)" //$medium-blue }; render() { return ( - <div className="type-outer" id={this.props.type + "-filter"} - onMouseEnter={() => this._hover = true} - onMouseLeave={() => this._hover = false} - onClick={this.onClick}> - <div className="type-icon" id={this.props.type + "-icon"} - style={this._hover ? this.hoverStyle : this._isSelected ? this.selected : this.notSelected} - > + <div className="type-outer" id={this.props.type + '-filter'} onMouseEnter={() => (this._hover = true)} onMouseLeave={() => (this._hover = false)} onClick={this.onClick}> + <div className="type-icon" id={this.props.type + '-icon'} style={this._hover ? this.hoverStyle : this._isSelected ? this.selected : this.notSelected}> <FontAwesomeIcon className="fontawesome-icon" icon={this.getIcon()} /> </div> {/* <div className="filter-description">{this.props.type}</div> */} </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/search/NaviconButton.scss b/src/client/views/search/NaviconButton.scss index 8a70b29de..13d31bbe9 100644 --- a/src/client/views/search/NaviconButton.scss +++ b/src/client/views/search/NaviconButton.scss @@ -1,4 +1,4 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables'; $height-icon: 15px; $width-line: 30px; @@ -6,7 +6,7 @@ $height-line: 4px; $transition-time: 0.4s; $rotation: 45deg; -$translateY: ($height-icon / 2); +$translateY: calc($height-icon / 2); $translateX: 0; #hamburger-icon { @@ -25,7 +25,7 @@ $translateX: 0; height: $height-line; position: absolute; left: 0; - border-radius: ($height-line / 2); + border-radius: calc($height-line / 2); transition: all $transition-time; -webkit-transition: all $transition-time; -moz-transition: all $transition-time; @@ -66,4 +66,4 @@ $translateX: 0; transform: scale(1.1); -webkit-transform: scale(1.1); -moz-transform: scale(1.1); -}
\ No newline at end of file +} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 74464181f..3172ff9c0 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,4 +1,4 @@ -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx index 2139919e0..39e778b76 100644 --- a/src/client/views/selectedDoc/SelectedDocView.tsx +++ b/src/client/views/selectedDoc/SelectedDocView.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ListBox } from 'browndash-components'; import { computed } from 'mobx'; diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index 524492226..093806127 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -11,7 +11,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './DashWebRTCVideo.scss'; import { hangup, initialize, refreshVideos } from './WebCamLogic'; -import React = require('react'); +import * as React from 'react'; /** * This models the component that will be rendered, that can be used as a doc that will reflect the video cams. diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index 17d3db8fd..c906dcc03 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { DocServer } from '../client/DocServer'; -import { Doc } from '../fields/Doc'; -import * as Pdfjs from "pdfjs-dist"; -import "pdfjs-dist/web/pdf_viewer.css"; -import { Utils } from '../Utils'; -const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); - -const protoId = "protoDoc"; -const delegateId = "delegateDoc"; +import * as ReactDOM from 'react-dom/client'; +console.log('ENTERED'); class Test extends React.Component { + render() { + return <div> HELLO WORLD </div>; + } } +const root = ReactDOM.createRoot(document.getElementById('root')!); + +root.render(<Test />); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index e60f7553f..6fb97d70c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -30,7 +30,7 @@ import { ComputedField, ScriptField } from './ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util'; -import JSZip = require('jszip'); +import * as JSZip from 'jszip'; export const LinkedTo = '-linkedTo'; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -816,6 +816,7 @@ export namespace Doc { } export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) { + if (infield instanceof Promise) return; if (!(infield instanceof Doc)) { infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system)); return; @@ -1201,14 +1202,14 @@ export namespace Doc { return !doc ? undefined : doc.isTemplateDoc - ? doc - : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc - ? doc.dragFactory - : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc - ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc - ? Doc.Layout(doc).proto - : Doc.Layout(doc) - : undefined; + ? doc + : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc + ? doc.dragFactory + : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc + ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc + ? Doc.Layout(doc).proto + : Doc.Layout(doc) + : undefined; } export function deiconifyView(doc: Doc) { diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts index f035eeb0d..f5e64ae1f 100644 --- a/src/fields/Schema.ts +++ b/src/fields/Schema.ts @@ -94,6 +94,7 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) } export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } { + return undefined as any; (schema as any).proto = Doc; return schema as any; } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index cd07a8885..62690a9fb 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -15,7 +15,7 @@ function optional(propSchema: PropSchema) { return custom( value => { if (value !== undefined) { - return propSchema.serializer(value); + return propSchema.serializer(value, '', undefined); // this function only takes one parameter, but I think its typescript typings are messed up to take 3 } return SKIP; }, @@ -97,7 +97,7 @@ export class ScriptField extends ObjectField { constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) { super(); - const captured = script?.options.capturedVariables; + const captured = script?.options?.capturedVariables; if (captured) { this.captures = new List<string>(Object.keys(captured).map(key => key + ':' + (captured[key] instanceof Doc ? 'ID->' + (captured[key] as Doc)[Id] : captured[key].toString()))); } @@ -151,7 +151,7 @@ export class ComputedField extends ScriptField { _lastComputedResult: any; //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); + _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); [ToValue](doc: Doc) { return ComputedField.toValue(doc, this); diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index da38fcaee..ee212e4af 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -5,45 +5,45 @@ import * as rp from 'request-promise'; import { DocServer } from '../client/DocServer'; import { Docs } from '../client/documents/Documents'; import { Networking } from '../client/Network'; -import { DFLT_IMAGE_NATIVE_DIM } from '../client/views/global/globalCssVariables.scss'; import { MainViewModal } from '../client/views/MainViewModal'; import { Doc, Opt } from '../fields/Doc'; import { List } from '../fields/List'; import { listSpec } from '../fields/Schema'; import { Cast } from '../fields/Types'; import { Utils } from '../Utils'; -import "./ImageUpload.scss"; +import './ImageUpload.scss'; import { MobileInterface } from './MobileInterface'; -import React = require('react'); - +import * as React from 'react'; +//import { DFLT_IMAGE_NATIVE_DIM } from '../client/views/global/globalCssVariables.scss'; +const DFLT_IMAGE_NATIVE_DIM = '50px'; export interface ImageUploadProps { Document: Doc; // Target document for upload (upload location) } const inputRef = React.createRef<HTMLInputElement>(); -const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); +const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); @observer export class Uploader extends React.Component<ImageUploadProps> { - @observable error: string = ""; - @observable nm: string = "Choose files"; // Text of 'Choose Files' button - @observable process: string = ""; // Current status of upload + @observable error: string = ''; + @observable nm: string = 'Choose files'; // Text of 'Choose Files' button + @observable process: string = ''; // Current status of upload onClick = async () => { try { const col = this.props.Document; await Docs.Prototypes.initialize(); - const imgPrev = document.getElementById("img_preview"); - this.setOpacity(1, "1"); // Slab 1 + const imgPrev = document.getElementById('img_preview'); + this.setOpacity(1, '1'); // Slab 1 if (imgPrev) { const files: FileList | null = inputRef.current!.files; - this.setOpacity(2, "1"); // Slab 2 + this.setOpacity(2, '1'); // Slab 2 if (files && files.length !== 0) { - this.process = "Uploading Files"; + this.process = 'Uploading Files'; for (let index = 0; index < files.length; ++index) { const file = files[index]; - const res = await Networking.UploadFilesToServer({file}); - this.setOpacity(3, "1"); // Slab 3 + const res = await Networking.UploadFilesToServer({ file }); + this.setOpacity(3, '1'); // Slab 3 // For each item that the user has selected res.map(async ({ result }) => { const name = file.name; @@ -53,19 +53,19 @@ export class Uploader extends React.Component<ImageUploadProps> { const path = result.accessPaths.agnostic.client; let doc = null; // Case 1: File is a video - if (file.type === "video/mp4") { + if (file.type === 'video/mp4') { doc = Docs.Create.VideoDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); // Case 2: File is a PDF document - } else if (file.type === "application/pdf") { + } else if (file.type === 'application/pdf') { doc = Docs.Create.PdfDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); // Case 3: File is another document type (most likely Image) } else { doc = Docs.Create.ImageDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); } - this.setOpacity(4, "1"); // Slab 4 - const res = await rp.get(Utils.prepend("/getUserDocumentIds")); + this.setOpacity(4, '1'); // Slab 4 + const res = await rp.get(Utils.prepend('/getUserDocumentIds')); if (!res) { - throw new Error("No user id returned"); + throw new Error('No user id returned'); } const field = await DocServer.GetRefField(JSON.parse(res).userDocumentId); let pending: Opt<Doc>; @@ -76,19 +76,19 @@ export class Uploader extends React.Component<ImageUploadProps> { const data = await Cast(pending.data, listSpec(Doc)); if (data) data.push(doc); else pending.data = new List([doc]); - this.setOpacity(5, "1"); // Slab 5 - this.process = "File " + (index + 1).toString() + " Uploaded"; - this.setOpacity(6, "1"); // Slab 6 + this.setOpacity(5, '1'); // Slab 5 + this.process = 'File ' + (index + 1).toString() + ' Uploaded'; + this.setOpacity(6, '1'); // Slab 6 } - if ((index + 1) === files.length) { - this.process = "Uploads Completed"; - this.setOpacity(7, "1"); // Slab 7 + if (index + 1 === files.length) { + this.process = 'Uploads Completed'; + this.setOpacity(7, '1'); // Slab 7 } }); } // Case in which the user pressed upload and no files were selected } else { - this.process = "No file selected"; + this.process = 'No file selected'; } // Three seconds after upload the menu will reset setTimeout(this.clearUpload, 3000); @@ -96,7 +96,7 @@ export class Uploader extends React.Component<ImageUploadProps> { } catch (error) { this.error = JSON.stringify(error); } - } + }; // Updates label after a files is selected (so user knows a file is uploaded) inputLabel = async () => { @@ -105,46 +105,48 @@ export class Uploader extends React.Component<ImageUploadProps> { if (files && files.length === 1) { this.nm = files[0].name; } else if (files && files.length > 1) { - this.nm = files.length.toString() + " files selected"; + this.nm = files.length.toString() + ' files selected'; } - } + }; // Loops through load icons, and resets buttons @action clearUpload = () => { for (let i = 1; i < 8; i++) { - this.setOpacity(i, "0.2"); + this.setOpacity(i, '0.2'); } - this.nm = "Choose files"; + this.nm = 'Choose files'; if (inputRef.current) { - inputRef.current.value = ""; + inputRef.current.value = ''; } - this.process = ""; - } + this.process = ''; + }; // Clears the upload and closes the upload menu closeUpload = () => { this.clearUpload(); MobileInterface.Instance.toggleUpload(); - } + }; // Handles the setting of the loading bar setOpacity = (index: number, opacity: string) => { - const slab = document.getElementById("slab" + index); + const slab = document.getElementById('slab' + index); if (slab) slab.style.opacity = opacity; - } + }; // Returns the upload interface for mobile private get uploadInterface() { return ( <div className="imgupload_cont"> <div className="closeUpload" onClick={() => this.closeUpload()}> - <FontAwesomeIcon icon="window-close" size={"lg"} /> + <FontAwesomeIcon icon="window-close" size={'lg'} /> </div> - <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: "130" }} /> - <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== "Choose files" ? "active" : ""}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input> - <label className="file" id="label" htmlFor="input_image_file">{this.nm}</label> + <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: '130' }} /> + <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== 'Choose files' ? 'active' : ''}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input> + <label className="file" id="label" htmlFor="input_image_file"> + {this.nm} + </label> <div className="upload_label" onClick={this.onClick}> Upload </div> @@ -167,16 +169,6 @@ export class Uploader extends React.Component<ImageUploadProps> { @observable private overlayOpacity = 0.4; render() { - return ( - <MainViewModal - contents={this.uploadInterface} - isDisplayed={true} - interactive={true} - dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} - overlayDisplayedOpacity={this.overlayOpacity} - closeOnExternalClick={this.closeUpload} - /> - ); + return <MainViewModal contents={this.uploadInterface} isDisplayed={true} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />; } - -}
\ No newline at end of file +} diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx index 6415099fd..2e595e4bc 100644 --- a/src/mobile/MobileInkOverlay.tsx +++ b/src/mobile/MobileInkOverlay.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { DocServer } from '../client/DocServer'; diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index bc8fd6f87..55b50cc12 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -4,9 +4,9 @@ import { createWriteStream, exists, mkdir, readFile, unlink, writeFile } from 'f import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; import * as path from 'path'; -import * as rimraf from "rimraf"; +import { rimraf } from "rimraf"; import { ExecOptions } from 'shelljs'; -import Mail = require('nodemailer/lib/mailer'); +import * as Mail from 'nodemailer/lib/mailer'; const projectRoot = path.resolve(__dirname, "../../"); export function pathFromRoot(relative?: string) { @@ -103,8 +103,10 @@ export const createIfNotExists = async (path: string) => { }; export async function Prune(rootDirectory: string): Promise<boolean> { - const error = await new Promise<Error>(resolve => rimraf(rootDirectory, resolve)); - return error === null; + // const error = await new Promise<Error>(resolve => rimraf(rootDirectory).then(resolve)); + await new Promise<void>(resolve => rimraf(rootDirectory).then(() => resolve())); + // return error === null; + return true; } export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => unlink(mediaPath, error => resolve(error === null))); diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index 46c0d8a8a..c6c4ca464 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -1,21 +1,19 @@ -import ApiManager, { Registration } from "./ApiManager"; -import { Method, _permission_denied } from "../RouteManager"; -import { WebSocket } from "../websocket"; -import { Database } from "../database"; -import rimraf = require("rimraf"); -import { filesDirectory } from ".."; -import { DashUploadUtils } from "../DashUploadUtils"; -import { mkdirSync } from "fs"; -import RouteSubscriber from "../RouteSubscriber"; +import ApiManager, { Registration } from './ApiManager'; +import { Method, _permission_denied } from '../RouteManager'; +import { WebSocket } from '../websocket'; +import { Database } from '../database'; +import { rimraf } from 'rimraf'; +import { filesDirectory } from '..'; +import { DashUploadUtils } from '../DashUploadUtils'; +import { mkdirSync } from 'fs'; +import RouteSubscriber from '../RouteSubscriber'; export default class DeleteManager extends ApiManager { - protected initialize(register: Registration): void { - register({ method: Method.GET, requireAdminInRelease: true, - subscription: new RouteSubscriber("delete").add("target?"), + subscription: new RouteSubscriber('delete').add('target?'), secureHandler: async ({ req, res }) => { const { target } = req.params; @@ -24,12 +22,12 @@ export default class DeleteManager extends ApiManager { } else { let all = false; switch (target) { - case "all": + case 'all': all = true; - case "database": + case 'database': await WebSocket.doDelete(false); if (!all) break; - case "files": + case 'files': rimraf.sync(filesDirectory); mkdirSync(filesDirectory); await DashUploadUtils.buildFileDirectories(); @@ -39,10 +37,8 @@ export default class DeleteManager extends ApiManager { } } - res.redirect("/home"); - } + res.redirect('/home'); + }, }); - } - -}
\ No newline at end of file +} diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 391f67bbb..c3b8643d3 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -11,7 +11,7 @@ import RouteSubscriber from '../RouteSubscriber'; import { AcceptableMedia, Upload } from '../SharedMediaTypes'; import ApiManager, { Registration } from './ApiManager'; import { SolrManager } from './SearchManager'; -import v4 = require('uuid/v4'); +import * as uuid from 'uuid'; import { DashVersion } from '../../fields/DocSymbols'; const AdmZip = require('adm-zip'); const imageDataUri = require('image-data-uri'); @@ -63,7 +63,7 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: '/uploadFormData', secureHandler: async ({ req, res }) => { - const form = new formidable.IncomingForm(); + const form = new formidable.IncomingForm({ keepExtensions: true, uploadDir: pathToDirectory(Directory.parsed_files) }); let fileguids = ''; let filesize = ''; form.on('field', (e: string, value: string) => { @@ -77,19 +77,19 @@ export default class UploadManager extends ApiManager { fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `upload starting`)); form.on('progress', e => fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `read:(${Math.round((100 * +e) / +filesize)}%) ${e} of ${filesize}`))); - form.keepExtensions = true; - form.uploadDir = pathToDirectory(Directory.parsed_files); return new Promise<void>(resolve => { form.parse(req, async (_err, _fields, files) => { const results: Upload.FileResponse[] = []; if (_err?.message) { results.push({ source: { + filepath: '', + originalFilename: 'none', + newFilename: 'none', + mimetype: 'text', size: 0, - path: 'none', - name: 'none', - type: 'none', - toJSON: () => ({ name: 'none', path: '' }), + hashAlgorithm: 'md5', + toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }), }, result: { name: 'failed upload', message: `${_err.message}` }, }); @@ -98,8 +98,8 @@ export default class UploadManager extends ApiManager { for (const key in files) { const f = files[key]; - if (!Array.isArray(f)) { - const result = await DashUploadUtils.upload(f, key); // key is the guid used by the client to track upload progress. + if (f) { + const result = await DashUploadUtils.upload(f[0], key); // key is the guid used by the client to track upload progress. result && !(result.result instanceof Error) && results.push(result); } } @@ -197,15 +197,14 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: '/uploadDoc', secureHandler: ({ req, res }) => { - const form = new formidable.IncomingForm(); - form.keepExtensions = true; + const form = new formidable.IncomingForm({ keepExtensions: true }); // let path = req.body.path; const ids: { [id: string]: string } = {}; let remap = true; const getId = (id: string): string => { if (!remap || id.endsWith('Proto')) return id; if (id in ids) return ids[id]; - return (ids[id] = v4()); + return (ids[id] = uuid.v4()); }; const mapFn = (doc: any) => { if (doc.id) { @@ -245,15 +244,16 @@ export default class UploadManager extends ApiManager { }; return new Promise<void>(resolve => { form.parse(req, async (_err, fields, files) => { - remap = fields.remap !== 'false'; + remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); //.remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false' let id: string = ''; let docids: string[] = []; let linkids: string[] = []; try { for (const name in files) { const f = files[name]; - const path_2 = Array.isArray(f) ? '' : f.path; - const zip = new AdmZip(path_2); + if (!f) continue; + const path_2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set? + const zip = new AdmZip(path_2.filepath); zip.getEntries().forEach((entry: any) => { let entryName = entry.entryName.replace(/%%%/g, '/'); if (!entryName.startsWith('files/')) { @@ -299,7 +299,7 @@ export default class UploadManager extends ApiManager { } catch (e) { console.log(e); } - unlink(path_2, () => {}); + unlink(path_2.filepath, () => {}); } SolrManager.update(); res.send(JSON.stringify({ id, docids, linkids } || 'error')); diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index 8b7994eac..0431b9bcf 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -7,6 +7,8 @@ import { Opt } from '../../fields/Doc'; import { WebSocket } from '../websocket'; import { resolvedPorts } from '../server_Initialization'; import { DashVersion } from '../../fields/DocSymbols'; +import { Utils } from '../../Utils'; +import { check, validationResult } from 'express-validator'; export const timeMap: { [id: string]: number } = {}; interface ActivityUnit { @@ -32,7 +34,7 @@ export default class UserManager extends ApiManager { secureHandler: async ({ user, req, res }) => { const result: any = {}; user.cacheDocumentIds = req.body.cacheDocumentIds; - user.save(err => { + user.save().then(undefined, err => { if (err) { result.error = [{ msg: 'Error while caching documents' }]; } @@ -49,7 +51,7 @@ export default class UserManager extends ApiManager { method: Method.GET, subscription: '/getUserDocumentIds', secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }), - publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }), + publicHandler: ({ res }) => res.send({ userDocumentId: Utils.GuestID(), linkDatabaseId: 3, sharingDocumentId: 2 }), }); register({ @@ -81,7 +83,7 @@ export default class UserManager extends ApiManager { resolvedPorts, }) ), - publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })), + publicHandler: ({ res }) => res.send(JSON.stringify({ userDocumentId: Utils.GuestID(), email: 'guest', resolvedPorts })), }); register({ @@ -107,15 +109,18 @@ export default class UserManager extends ApiManager { return; } - req.assert('new_pass', 'Password must be at least 4 characters long').len({ min: 4 }); - req.assert('new_confirm', 'Passwords do not match').equals(new_pass); + check('new_pass', 'Password must be at least 4 characters long') + .run(req) + .then(chcekcres => console.log(chcekcres)); //.len({ min: 4 }); + check('new_confirm', 'Passwords do not match') + .run(req) + .then(theres => console.log(theres)); //.equals(new_pass); if (curr_pass === new_pass) { result.error = [{ msg: 'Current and new password are the same' }]; } - // was there error in validating new passwords? - if (req.validationErrors()) { - // was there error? - result.error = req.validationErrors(); + if (validationResult(req).array().length) { + // was there error in validating new passwords? + result.error = validationResult(req); } // will only change password if there are no errors. @@ -125,7 +130,7 @@ export default class UserManager extends ApiManager { user.passwordResetExpires = undefined; } - user.save(err => { + user.save().then(undefined, err => { if (err) { result.error = [{ msg: 'Error while saving new password' }]; } diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts index 1a5934d8f..1ef7a131d 100644 --- a/src/server/DashSession/DashSessionAgent.ts +++ b/src/server/DashSession/DashSessionAgent.ts @@ -1,18 +1,18 @@ -import { Email, pathFromRoot } from "../ActionUtilities"; -import { red, yellow, green, cyan } from "colors"; -import { get } from "request-promise"; -import { Utils } from "../../Utils"; -import { WebSocket } from "../websocket"; -import { MessageStore } from "../Message"; -import { launchServer, onWindows } from ".."; -import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from "fs"; -import * as Archiver from "archiver"; -import { resolve } from "path"; -import rimraf = require("rimraf"); -import { AppliedSessionAgent, ExitHandler } from "./Session/agents/applied_session_agent"; -import { ServerWorker } from "./Session/agents/server_worker"; -import { Monitor } from "./Session/agents/monitor"; -import { MessageHandler, ErrorLike } from "./Session/agents/promisified_ipc_manager"; +import { Email, pathFromRoot } from '../ActionUtilities'; +import { red, yellow, green, cyan } from 'colors'; +import { get } from 'request-promise'; +import { Utils } from '../../Utils'; +import { WebSocket } from '../websocket'; +import { MessageStore } from '../Message'; +import { launchServer, onWindows } from '..'; +import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from 'fs'; +import * as Archiver from 'archiver'; +import { resolve } from 'path'; +import { rimraf } from 'rimraf'; +import { AppliedSessionAgent, ExitHandler } from './Session/agents/applied_session_agent'; +import { ServerWorker } from './Session/agents/server_worker'; +import { Monitor } from './Session/agents/monitor'; +import { MessageHandler, ErrorLike } from './Session/agents/promisified_ipc_manager'; /** * If we're the monitor (master) thread, we should launch the monitor logic for the session. @@ -20,9 +20,8 @@ import { MessageHandler, ErrorLike } from "./Session/agents/promisified_ipc_mana * our job should be to run the server. */ export class DashSessionAgent extends AppliedSessionAgent { - - private readonly signature = "-Dash Server Session Manager"; - private readonly releaseDesktop = pathFromRoot("../../Desktop"); + private readonly signature = '-Dash Server Session Manager'; + private readonly releaseDesktop = pathFromRoot('../../Desktop'); /** * The core method invoked when the single master thread is initialized. @@ -31,13 +30,13 @@ export class DashSessionAgent extends AppliedSessionAgent { protected async initializeMonitor(monitor: Monitor): Promise<string> { const sessionKey = Utils.GenerateGuid(); await this.dispatchSessionPassword(sessionKey); - monitor.addReplCommand("pull", [], () => monitor.exec("git pull")); - monitor.addReplCommand("solr", [/start|stop|index/], this.executeSolrCommand); - monitor.addReplCommand("backup", [], this.backup); - monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to)); - monitor.on("backup", this.backup); - monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to)); - monitor.on("delete", WebSocket.doDelete); + monitor.addReplCommand('pull', [], () => monitor.exec('git pull')); + monitor.addReplCommand('solr', [/start|stop|index/], this.executeSolrCommand); + monitor.addReplCommand('backup', [], this.backup); + monitor.addReplCommand('debug', [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to)); + monitor.on('backup', this.backup); + monitor.on('debug', async ({ to }) => this.dispatchZippedDebugBackup(to)); + monitor.on('delete', WebSocket.doDelete); monitor.coreHooks.onCrashDetected(this.dispatchCrashReport); return sessionKey; } @@ -58,13 +57,13 @@ export class DashSessionAgent extends AppliedSessionAgent { private _remoteDebugInstructions: string | undefined; private generateDebugInstructions = (zipName: string, target: string): string => { if (!this._remoteDebugInstructions) { - this._remoteDebugInstructions = readFileSync(resolve(__dirname, "./templates/remote_debug_instructions.txt"), { encoding: "utf8" }); + this._remoteDebugInstructions = readFileSync(resolve(__dirname, './templates/remote_debug_instructions.txt'), { encoding: 'utf8' }); } return this._remoteDebugInstructions .replace(/__zipname__/, zipName) .replace(/__target__/, target) .replace(/__signature__/, this.signature); - } + }; /** * Prepares the body of the email with information regarding a crash event. @@ -72,12 +71,12 @@ export class DashSessionAgent extends AppliedSessionAgent { private _crashInstructions: string | undefined; private generateCrashInstructions({ name, message, stack }: ErrorLike): string { if (!this._crashInstructions) { - this._crashInstructions = readFileSync(resolve(__dirname, "./templates/crash_instructions.txt"), { encoding: "utf8" }); + this._crashInstructions = readFileSync(resolve(__dirname, './templates/crash_instructions.txt'), { encoding: 'utf8' }); } return this._crashInstructions - .replace(/__name__/, name || "[no error name found]") - .replace(/__message__/, message || "[no error message found]") - .replace(/__stack__/, stack || "[no error stack found]") + .replace(/__name__/, name || '[no error name found]') + .replace(/__message__/, message || '[no error message found]') + .replace(/__stack__/, stack || '[no error stack found]') .replace(/__signature__/, this.signature); } @@ -88,23 +87,19 @@ export class DashSessionAgent extends AppliedSessionAgent { private dispatchSessionPassword = async (sessionKey: string): Promise<void> => { const { mainLog } = this.sessionMonitor; const { notificationRecipient } = DashSessionAgent; - mainLog(green("dispatching session key...")); + mainLog(green('dispatching session key...')); const error = await Email.dispatch({ to: notificationRecipient, - subject: "Dash Release Session Admin Authentication Key", - content: [ - `Here's the key for this session (started @ ${new Date().toUTCString()}):`, - sessionKey, - this.signature - ].join("\n\n") + subject: 'Dash Release Session Admin Authentication Key', + content: [`Here's the key for this session (started @ ${new Date().toUTCString()}):`, sessionKey, this.signature].join('\n\n'), }); if (error) { this.sessionMonitor.mainLog(red(`dispatch failure @ ${notificationRecipient} (${yellow(error.message)})`)); - mainLog(red("distribution of session key experienced errors")); + mainLog(red('distribution of session key experienced errors')); } else { - mainLog(green("successfully distributed session key to recipients")); + mainLog(green('successfully distributed session key to recipients')); } - } + }; /** * This sends an email with the generated crash report. @@ -114,37 +109,37 @@ export class DashSessionAgent extends AppliedSessionAgent { const { notificationRecipient } = DashSessionAgent; const error = await Email.dispatch({ to: notificationRecipient, - subject: "Dash Web Server Crash", - content: this.generateCrashInstructions(crashCause) + subject: 'Dash Web Server Crash', + content: this.generateCrashInstructions(crashCause), }); if (error) { this.sessionMonitor.mainLog(red(`dispatch failure @ ${notificationRecipient} ${yellow(`(${error.message})`)}`)); - mainLog(red("distribution of crash notification experienced errors")); + mainLog(red('distribution of crash notification experienced errors')); } else { - mainLog(green("successfully distributed crash notification to recipients")); + mainLog(green('successfully distributed crash notification to recipients')); } - } + }; /** - * Logic for interfacing with Solr. Either starts it, + * Logic for interfacing with Solr. Either starts it, * stops it, or rebuilds its indices. */ private executeSolrCommand = async (args: string[]): Promise<void> => { const { exec, mainLog } = this.sessionMonitor; const action = args[0]; - if (action === "index") { - exec("npx ts-node ./updateSearch.ts", { cwd: pathFromRoot("./src/server") }); + if (action === 'index') { + exec('npx ts-node ./updateSearch.ts', { cwd: pathFromRoot('./src/server') }); } else { - const command = `${onWindows ? "solr.cmd" : "solr"} ${args[0] === "start" ? "start" : "stop -p 8983"}`; - await exec(command, { cwd: "./solr-8.3.1/bin" }); + const command = `${onWindows ? 'solr.cmd' : 'solr'} ${args[0] === 'start' ? 'start' : 'stop -p 8983'}`; + await exec(command, { cwd: './solr-8.3.1/bin' }); try { - await get("http://localhost:8983"); - mainLog(green("successfully connected to 8983 after running solr initialization")); + await get('http://localhost:8983'); + mainLog(green('successfully connected to 8983 after running solr initialization')); } catch { - mainLog(red("unable to connect at 8983 after running solr initialization")); + mainLog(red('unable to connect at 8983 after running solr initialization')); } } - } + }; /** * Broadcast to all clients that their connection @@ -153,16 +148,16 @@ export class DashSessionAgent extends AppliedSessionAgent { private notifyClient: ExitHandler = reason => { const { _socket } = WebSocket; if (_socket) { - const message = typeof reason === "boolean" ? (reason ? "exit" : "temporary") : "crash"; + const message = typeof reason === 'boolean' ? (reason ? 'exit' : 'temporary') : 'crash'; Utils.Emit(_socket, MessageStore.ConnectionTerminated, message); } - } + }; /** * Performs a backup of the database, saved to the desktop subdirectory. * This should work as is only on our specific release server. */ - private backup = async (): Promise<void> => this.sessionMonitor.exec("backup.bat", { cwd: this.releaseDesktop }); + private backup = async (): Promise<void> => this.sessionMonitor.exec('backup.bat', { cwd: this.releaseDesktop }); /** * Compress either a brand new backup or the most recent backup and send it @@ -175,15 +170,17 @@ export class DashSessionAgent extends AppliedSessionAgent { try { // if desired, complete an immediate backup to send await this.backup(); - mainLog("backup complete"); + mainLog('backup complete'); const backupsDirectory = `${this.releaseDesktop}/backups`; // sort all backups by their modified time, and choose the most recent one - const target = readdirSync(backupsDirectory).map(filename => ({ - modifiedTime: statSync(`${backupsDirectory}/${filename}`).mtimeMs, - filename - })).sort((a, b) => b.modifiedTime - a.modifiedTime)[0].filename; + const target = readdirSync(backupsDirectory) + .map(filename => ({ + modifiedTime: statSync(`${backupsDirectory}/${filename}`).mtimeMs, + filename, + })) + .sort((a, b) => b.modifiedTime - a.modifiedTime)[0].filename; mainLog(`targeting ${target}...`); // create a zip file and to it, write the contents of the backup directory @@ -202,28 +199,25 @@ export class DashSessionAgent extends AppliedSessionAgent { to, subject: `Remote debug: compressed backup of ${target}...`, content: this.generateDebugInstructions(zipName, target), - attachments: [{ filename: zipName, path: zipPath }] + attachments: [{ filename: zipName, path: zipPath }], }); - // since this is intended to be a zero-footprint operation, clean up + // since this is intended to be a zero-footprint operation, clean up // by unlinking both the backup generated earlier in the function and the compressed zip file. // to generate a persistent backup, just run backup. unlinkSync(zipPath); rimraf.sync(targetPath); // indicate success or failure - mainLog(`${error === null ? green("successfully dispatched") : red("failed to dispatch")} ${zipName} to ${cyan(to)}`); + mainLog(`${error === null ? green('successfully dispatched') : red('failed to dispatch')} ${zipName} to ${cyan(to)}`); error && mainLog(red(error.message)); } catch (error: any) { - mainLog(red("unable to dispatch zipped backup...")); + mainLog(red('unable to dispatch zipped backup...')); mainLog(red(error.message)); } } - } export namespace DashSessionAgent { - - export const notificationRecipient = "browndashptc@gmail.com"; - + export const notificationRecipient = 'browndashptc@gmail.com'; } diff --git a/src/server/DashSession/Session/agents/applied_session_agent.ts b/src/server/DashSession/Session/agents/applied_session_agent.ts index 8339a06dc..2037e93e5 100644 --- a/src/server/DashSession/Session/agents/applied_session_agent.ts +++ b/src/server/DashSession/Session/agents/applied_session_agent.ts @@ -1,7 +1,8 @@ -import { isMaster } from "cluster"; +import * as _cluster from "cluster"; import { Monitor } from "./monitor"; import { ServerWorker } from "./server_worker"; -import { Utilities } from "../utilities/utilities"; +const cluster = _cluster as any; +const isMaster = cluster.isPrimary; export type ExitHandler = (reason: Error | boolean) => void | Promise<void>; @@ -15,13 +16,13 @@ export abstract class AppliedSessionAgent { private launched = false; public killSession = (reason: string, graceful = true, errorCode = 0) => { - const target = isMaster ? this.sessionMonitor : this.serverWorker; + const target = cluster.default.isPrimary ? this.sessionMonitor : this.serverWorker; target.killSession(reason, graceful, errorCode); } private sessionMonitorRef: Monitor | undefined; public get sessionMonitor(): Monitor { - if (!isMaster) { + if (!cluster.default.isPrimary) { this.serverWorker.emit("kill", { graceful: false, reason: "Cannot access the session monitor directly from the server worker thread.", diff --git a/src/server/DashSession/Session/agents/monitor.ts b/src/server/DashSession/Session/agents/monitor.ts index 9cb5ab576..a6fde4356 100644 --- a/src/server/DashSession/Session/agents/monitor.ts +++ b/src/server/DashSession/Session/agents/monitor.ts @@ -1,15 +1,21 @@ -import { ExitHandler } from "./applied_session_agent"; -import { Configuration, configurationSchema, defaultConfig, Identifiers, colorMapping } from "../utilities/session_config"; -import Repl, { ReplAction } from "../utilities/repl"; -import { isWorker, setupMaster, on, Worker, fork } from "cluster"; -import { manage, MessageHandler, ErrorLike } from "./promisified_ipc_manager"; -import { red, cyan, white, yellow, blue } from "colors"; -import { exec, ExecOptions } from "child_process"; -import { validate, ValidationError } from "jsonschema"; -import { Utilities } from "../utilities/utilities"; -import { readFileSync } from "fs"; -import IPCMessageReceiver from "./process_message_router"; -import { ServerWorker } from "./server_worker"; +import { ExitHandler } from './applied_session_agent'; +import { Configuration, configurationSchema, defaultConfig, Identifiers, colorMapping } from '../utilities/session_config'; +import Repl, { ReplAction } from '../utilities/repl'; +import * as _cluster from 'cluster'; +import { Worker } from 'cluster'; +import { manage, MessageHandler, ErrorLike } from './promisified_ipc_manager'; +import { red, cyan, white, yellow, blue } from 'colors'; +import { exec, ExecOptions } from 'child_process'; +import { validate, ValidationError } from 'jsonschema'; +import { Utilities } from '../utilities/utilities'; +import { readFileSync } from 'fs'; +import IPCMessageReceiver from './process_message_router'; +import { ServerWorker } from './server_worker'; +const cluster = _cluster as any; +const isWorker = cluster.isWorker; +const setupMaster = cluster.setupPrimary; +const on = cluster.on; +const fork = cluster.fork; /** * Validates and reads the configuration file, accordingly builds a child process factory @@ -26,14 +32,14 @@ export class Monitor extends IPCMessageReceiver { public static Create() { if (isWorker) { - ServerWorker.IPCManager.emit("kill", { - reason: "cannot create a monitor on the worker process.", + ServerWorker.IPCManager.emit('kill', { + reason: 'cannot create a monitor on the worker process.', graceful: false, - errorCode: 1 + errorCode: 1, }); process.exit(1); } else if (++Monitor.count > 1) { - console.error(red("cannot create more than one monitor.")); + console.error(red('cannot create more than one monitor.')); process.exit(1); } else { return new Monitor(); @@ -42,7 +48,7 @@ export class Monitor extends IPCMessageReceiver { private constructor() { super(); - console.log(this.timestamp(), cyan("initializing session...")); + console.log(this.timestamp(), cyan('initializing session...')); this.configureInternalHandlers(); this.config = this.loadAndValidateConfiguration(); this.initializeClusterFunctions(); @@ -53,8 +59,8 @@ export class Monitor extends IPCMessageReceiver { // handle exceptions in the master thread - there shouldn't be many of these // the IPC (inter process communication) channel closed exception can't seem // to be caught in a try catch, and is inconsequential, so it is ignored - process.on("uncaughtException", ({ message, stack }): void => { - if (message !== "Channel closed") { + process.on('uncaughtException', ({ message, stack }): void => { + if (message !== 'Channel closed') { this.mainLog(red(message)); if (stack) { this.mainLog(`uncaught exception\n${red(stack)}`); @@ -62,36 +68,36 @@ export class Monitor extends IPCMessageReceiver { } }); - this.on("kill", ({ reason, graceful, errorCode }) => this.killSession(reason, graceful, errorCode)); - this.on("lifecycle", ({ event }) => console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${event})`)); - } + this.on('kill', ({ reason, graceful, errorCode }) => this.killSession(reason, graceful, errorCode)); + this.on('lifecycle', ({ event }) => console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${event})`)); + }; private initializeClusterFunctions = () => { // determines whether or not we see the compilation / initialization / runtime output of each child server process - const output = this.config.showServerOutput ? "inherit" : "ignore"; - setupMaster({ stdio: ["ignore", output, output, "ipc"] }); + const output = this.config.showServerOutput ? 'inherit' : 'ignore'; + setupMaster({ stdio: ['ignore', output, output, 'ipc'] }); // a helpful cluster event called on the master thread each time a child process exits - on("exit", ({ process: { pid } }, code, signal) => { - const prompt = `server worker with process id ${pid} has exited with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.`; + on('exit', ({ process: { pid } }: { process: { pid: any } }, code: any, signal: any) => { + const prompt = `server worker with process id ${pid} has exited with code ${code}${signal === null ? '' : `, having encountered signal ${signal}`}.`; this.mainLog(cyan(prompt)); // to make this a robust, continuous session, every time a child process dies, we immediately spawn a new one this.spawn(); }); - } + }; public finalize = (sessionKey: string): void => { if (this.finalized) { - throw new Error("Session monitor is already finalized"); + throw new Error('Session monitor is already finalized'); } this.finalized = true; this.key = sessionKey; this.spawn(); - } + }; public readonly coreHooks = Object.freeze({ onCrashDetected: (listener: MessageHandler<{ error: ErrorLike }>) => this.on(Monitor.IntrinsicEvents.CrashDetected, listener), - onServerRunning: (listener: MessageHandler<{ isFirstTime: boolean }>) => this.on(Monitor.IntrinsicEvents.ServerRunning, listener) + onServerRunning: (listener: MessageHandler<{ isFirstTime: boolean }>) => this.on(Monitor.IntrinsicEvents.ServerRunning, listener), }); /** @@ -101,12 +107,12 @@ export class Monitor extends IPCMessageReceiver { * requests to complete) or immediately. */ public killSession = async (reason: string, graceful = true, errorCode = 0) => { - this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`)); - this.mainLog(`session exit reason: ${(red(reason))}`); + this.mainLog(cyan(`exiting session ${graceful ? 'clean' : 'immediate'}ly`)); + this.mainLog(`session exit reason: ${red(reason)}`); await this.executeExitHandlers(true); await this.killActiveWorker(graceful, true); process.exit(errorCode); - } + }; /** * Execute the list of functions registered to be called @@ -120,27 +126,27 @@ export class Monitor extends IPCMessageReceiver { */ public addReplCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => { this.repl.registerCommand(basename, argPatterns, action); - } + }; public exec = (command: string, options?: ExecOptions) => { return new Promise<void>(resolve => { - exec(command, { ...options, encoding: "utf8" }, (error, stdout, stderr) => { + exec(command, { ...options, encoding: 'utf8' }, (error, stdout, stderr) => { if (error) { this.execLog(red(`unable to execute ${white(command)}`)); - error.message.split("\n").forEach(line => line.length && this.execLog(red(`(error) ${line}`))); + error.message.split('\n').forEach(line => line.length && this.execLog(red(`(error) ${line}`))); } else { let outLines: string[], errorLines: string[]; - if ((outLines = stdout.split("\n").filter(line => line.length)).length) { + if ((outLines = stdout.split('\n').filter(line => line.length)).length) { outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`))); } - if ((errorLines = stderr.split("\n").filter(line => line.length)).length) { + if ((errorLines = stderr.split('\n').filter(line => line.length)).length) { errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`))); } } resolve(); }); }); - } + }; /** * Generates a blue UTC string associated with the time @@ -153,14 +159,14 @@ export class Monitor extends IPCMessageReceiver { */ public mainLog = (...optionalParams: any[]) => { console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams); - } + }; /** * A formatted, identified and timestamped log in color for non- */ private execLog = (...optionalParams: any[]) => { console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams); - } + }; /** * Reads in configuration .json file only once, in the master thread @@ -169,28 +175,28 @@ export class Monitor extends IPCMessageReceiver { private loadAndValidateConfiguration = (): Configuration => { let config: Configuration | undefined; try { - console.log(this.timestamp(), cyan("validating configuration...")); + console.log(this.timestamp(), cyan('validating configuration...')); config = JSON.parse(readFileSync('./session.config.json', 'utf8')); const options = { throwError: true, - allowUnknownAttributes: false + allowUnknownAttributes: false, }; // ensure all necessary and no excess information is specified by the configuration file validate(config, configurationSchema, options); config = Utilities.preciseAssign({}, defaultConfig, config); } catch (error: any) { if (error instanceof ValidationError) { - console.log(red("\nSession configuration failed.")); - console.log("The given session.config.json configuration file is invalid."); + console.log(red('\nSession configuration failed.')); + console.log('The given session.config.json configuration file is invalid.'); console.log(`${error.instance}: ${error.stack}`); process.exit(0); - } else if (error.code === "ENOENT" && error.path === "./session.config.json") { - console.log(cyan("Loading default session parameters...")); - console.log("Consider including a session.config.json configuration file in your project root for customization."); + } else if (error.code === 'ENOENT' && error.path === './session.config.json') { + console.log(cyan('Loading default session parameters...')); + console.log('Consider including a session.config.json configuration file in your project root for customization.'); config = Utilities.preciseAssign({}, defaultConfig); } else { - console.log(red("\nSession configuration failed.")); - console.log("The following unknown error occurred during configuration."); + console.log(red('\nSession configuration failed.')); + console.log('The following unknown error occurred during configuration.'); console.log(error.stack); process.exit(0); } @@ -203,7 +209,7 @@ export class Monitor extends IPCMessageReceiver { }); return config!; } - } + }; /** * Builds the repl that allows the following commands to be typed into stdin of the master thread. @@ -213,24 +219,24 @@ export class Monitor extends IPCMessageReceiver { const boolean = /true|false/; const number = /\d+/; const letters = /[a-zA-Z]+/; - repl.registerCommand("exit", [/clean|force/], args => this.killSession("manual exit requested by repl", args[0] === "clean", 0)); - repl.registerCommand("restart", [/clean|force/], args => this.killActiveWorker(args[0] === "clean")); - repl.registerCommand("set", [letters, "port", number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === "true")); - repl.registerCommand("set", [/polling/, number, boolean], args => { + repl.registerCommand('exit', [/clean|force/], args => this.killSession('manual exit requested by repl', args[0] === 'clean', 0)); + repl.registerCommand('restart', [/clean|force/], args => this.killActiveWorker(args[0] === 'clean')); + repl.registerCommand('set', [letters, 'port', number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === 'true')); + repl.registerCommand('set', [/polling/, number, boolean], args => { const newPollingIntervalSeconds = Math.floor(Number(args[1])); if (newPollingIntervalSeconds < 0) { - this.mainLog(red("the polling interval must be a non-negative integer")); + this.mainLog(red('the polling interval must be a non-negative integer')); } else { if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) { this.config.polling.intervalSeconds = newPollingIntervalSeconds; - if (args[2] === "true") { - Monitor.IPCManager.emit("updatePollingInterval", { newPollingIntervalSeconds }); + if (args[2] === 'true') { + Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds }); } } } }); return repl; - } + }; private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason))); @@ -240,13 +246,13 @@ export class Monitor extends IPCMessageReceiver { private killActiveWorker = async (graceful = true, isSessionEnd = false): Promise<void> => { if (this.activeWorker && !this.activeWorker.isDead()) { if (graceful) { - Monitor.IPCManager.emit("manualExit", { isSessionEnd }); + Monitor.IPCManager.emit('manualExit', { isSessionEnd }); } else { await ServerWorker.IPCManager.destroy(); this.activeWorker.process.kill(); } } - } + }; /** * Allows the caller to set the port at which the target (be it the server, @@ -255,7 +261,7 @@ export class Monitor extends IPCMessageReceiver { * at the port. Otherwise, the updated port won't be used until / unless the child * dies on its own and triggers a restart. */ - private setPort = (port: "server" | "socket" | string, value: number, immediateRestart: boolean): void => { + private setPort = (port: 'server' | 'socket' | string, value: number, immediateRestart: boolean): void => { if (value > 1023 && value < 65536) { this.config.ports[port] = value; if (immediateRestart) { @@ -264,7 +270,7 @@ export class Monitor extends IPCMessageReceiver { } else { this.mainLog(red(`${port} is an invalid port number`)); } - } + }; /** * Kills the current active worker and proceeds to spawn a new worker, @@ -272,27 +278,29 @@ export class Monitor extends IPCMessageReceiver { */ private spawn = async (): Promise<void> => { await this.killActiveWorker(); - const { config: { polling, ports }, key } = this; + const { + config: { polling, ports }, + key, + } = this; this.activeWorker = fork({ pollingRoute: polling.route, pollingFailureTolerance: polling.failureTolerance, serverPort: ports.server, socketPort: ports.socket, pollingIntervalSeconds: polling.intervalSeconds, - session_key: key + session_key: key, }); - Monitor.IPCManager = manage(this.activeWorker.process, this.handlers); + if (this.activeWorker) { + Monitor.IPCManager = manage(this.activeWorker.process, this.handlers); + } this.mainLog(cyan(`spawned new server worker with process id ${this.activeWorker?.process.pid}`)); - } - + }; } export namespace Monitor { - export enum IntrinsicEvents { - KeyGenerated = "key_generated", - CrashDetected = "crash_detected", - ServerRunning = "server_running" + KeyGenerated = 'key_generated', + CrashDetected = 'crash_detected', + ServerRunning = 'server_running', } - -}
\ No newline at end of file +} diff --git a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts index f6c8de521..76e218977 100644 --- a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts +++ b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts @@ -1,9 +1,9 @@ -import { Utilities } from "../utilities/utilities"; -import { ChildProcess } from "child_process"; +import { Utilities } from '../utilities/utilities'; +import { ChildProcess } from 'child_process'; /** * Convenience constructor - * @param target the process / worker to which to attach the specialized listeners + * @param target the process / worker to which to attach the specialized listeners */ export function manage(target: IPCTarget, handlers?: HandlerMap) { return new PromisifiedIPCManager(target, handlers); @@ -18,26 +18,30 @@ export type HandlerMap = { [name: string]: MessageHandler[] }; /** * This will always literally be a child process. But, though setting * up a manager in the parent will indeed see the target as the ChildProcess, - * setting up a manager in the child will just see itself as a regular NodeJS.Process. + * setting up a manager in the child will just see itself as a regular NodeJS.Process. */ export type IPCTarget = NodeJS.Process | ChildProcess; /** - * Specifies a general message format for this API + * Specifies a general message format for this API */ export type Message<T = any> = { name: string; args?: T; }; -export type MessageHandler<T = any> = (args: T) => (any | Promise<any>); +export type MessageHandler<T = any> = (args: T) => any | Promise<any>; /** * When a message is emitted, it is embedded with private metadata * to facilitate the resolution of promises, etc. */ -interface InternalMessage extends Message { metadata: Metadata; } -interface Metadata { isResponse: boolean; id: string; } -type InternalMessageHandler = (message: InternalMessage) => (any | Promise<any>); +interface InternalMessage extends Message { + metadata: Metadata; +} +interface Metadata { + isResponse: boolean; + id: string; +} /** * Allows for the transmission of the error's key features over IPC. @@ -56,12 +60,12 @@ export interface Response<T = any> { error?: ErrorLike; } -const destroyEvent = "__destroy__"; +const destroyEvent = '__destroy__'; /** * This is a wrapper utility class that allows the caller process * to emit an event and return a promise that resolves when it and all - * other processes listening to its emission of this event have completed. + * other processes listening to its emission of this event have completed. */ export class PromisifiedIPCManager { private readonly target: IPCTarget; @@ -75,7 +79,7 @@ export class PromisifiedIPCManager { this.target = target; if (handlers) { handlers[destroyEvent] = [this.destroyHelper]; - this.target.addListener("message", this.generateInternalHandler(handlers)); + this.target.addListener('message', this.generateInternalHandler(handlers)); } } @@ -86,26 +90,27 @@ export class PromisifiedIPCManager { */ public emit = async <T = any>(name: string, args?: any): Promise<Response<T>> => { if (this.isDestroyed) { - const error = { name: "FailedDispatch", message: "Cannot use a destroyed IPC manager to emit a message." }; + const error = { name: 'FailedDispatch', message: 'Cannot use a destroyed IPC manager to emit a message.' }; return { error }; } return new Promise<Response<T>>(resolve => { const messageId = Utilities.guid(); + type InternalMessageHandler = (message: any /* MessageListener*/) => any | Promise<any>; const responseHandler: InternalMessageHandler = ({ metadata: { id, isResponse }, args }) => { if (isResponse && id === messageId) { - this.target.removeListener("message", responseHandler); + this.target.removeListener('message', responseHandler); resolve(args); } }; - this.target.addListener("message", responseHandler); + this.target.addListener('message', responseHandler); const message = { name, args, metadata: { id: messageId, isResponse: false } }; if (!(this.target.send && this.target.send(message))) { - const error: ErrorLike = { name: "FailedDispatch", message: "Either the target's send method was undefined or the act of sending failed." }; + const error: ErrorLike = { name: 'FailedDispatch', message: "Either the target's send method was undefined or the act of sending failed." }; resolve({ error }); - this.target.removeListener("message", responseHandler); + this.target.removeListener('message', responseHandler); } }); - } + }; /** * Invoked from either the parent or the child process, this allows @@ -122,7 +127,7 @@ export class PromisifiedIPCManager { } resolve(); }); - } + }; /** * Dispatches the dummy responses and sets the isDestroyed flag to true. @@ -131,12 +136,12 @@ export class PromisifiedIPCManager { const { pendingMessages } = this; this.isDestroyed = true; Object.keys(pendingMessages).forEach(id => { - const error: ErrorLike = { name: "ManagerDestroyed", message: "The IPC manager was destroyed before the response could be returned." }; + const error: ErrorLike = { name: 'ManagerDestroyed', message: 'The IPC manager was destroyed before the response could be returned.' }; const message: InternalMessage = { name: pendingMessages[id], args: { error }, metadata: { id, isResponse: true } }; this.target.send?.(message); }); this.pendingMessages = {}; - } + }; /** * This routine receives a uniquely identified message. If the message is itself a response, @@ -145,29 +150,30 @@ export class PromisifiedIPCManager { * which will ultimately invoke the responseHandler of the original emission and resolve the * sender's promise. */ - private generateInternalHandler = (handlers: HandlerMap): MessageHandler => async (message: InternalMessage) => { - const { name, args, metadata } = message; - if (name && metadata && !metadata.isResponse) { - const { id } = metadata; - this.pendingMessages[id] = name; - let error: Error | undefined; - let results: any[] | undefined; - try { - const registered = handlers[name]; - if (registered) { - results = await Promise.all(registered.map(handler => handler(args))); + private generateInternalHandler = + (handlers: HandlerMap): MessageHandler => + async (message: InternalMessage) => { + const { name, args, metadata } = message; + if (name && metadata && !metadata.isResponse) { + const { id } = metadata; + this.pendingMessages[id] = name; + let error: Error | undefined; + let results: any[] | undefined; + try { + const registered = handlers[name]; + if (registered) { + results = await Promise.all(registered.map(handler => handler(args))); + } + } catch (e: any) { + error = e; + } + if (!this.isDestroyed && this.target.send) { + const metadata = { id, isResponse: true }; + const response: Response = { results, error }; + const message = { name, args: response, metadata }; + delete this.pendingMessages[id]; + this.target.send(message); } - } catch (e: any) { - error = e; - } - if (!this.isDestroyed && this.target.send) { - const metadata = { id, isResponse: true }; - const response: Response = { results, error }; - const message = { name, args: response, metadata }; - delete this.pendingMessages[id]; - this.target.send(message); } - } - } - -}
\ No newline at end of file + }; +} diff --git a/src/server/DashSession/Session/agents/server_worker.ts b/src/server/DashSession/Session/agents/server_worker.ts index 634b0113d..d8b3ee80b 100644 --- a/src/server/DashSession/Session/agents/server_worker.ts +++ b/src/server/DashSession/Session/agents/server_worker.ts @@ -1,4 +1,4 @@ -import { isMaster } from "cluster"; +import cluster from "cluster"; import { green, red, white, yellow } from "colors"; import { get } from "request-promise"; import { ExitHandler } from "./applied_session_agent"; @@ -22,7 +22,7 @@ export class ServerWorker extends IPCMessageReceiver { private serverPort: number; private isInitialized = false; public static Create(work: Function) { - if (isMaster) { + if (cluster.isPrimary) { console.error(red("cannot create a worker on the monitor process.")); process.exit(1); } else if (++ServerWorker.count > 1) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 2053ae448..7765349ff 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -14,8 +14,8 @@ import { createIfNotExists } from './ActionUtilities'; import { clientPathToFile, Directory, pathToDirectory, serverPathToFile } from './ApiManagers/UploadManager'; import { resolvedServerUrl } from './server_Initialization'; import { AcceptableMedia, Upload } from './SharedMediaTypes'; -import request = require('request-promise'); -import formidable = require('formidable'); +import * as request from 'request-promise'; +import * as formidable from 'formidable'; import { AzureManager } from './ApiManagers/AzureManager'; import axios from 'axios'; const spawn = require('child_process').spawn; @@ -119,14 +119,14 @@ export namespace DashUploadUtils { }; } - function resolveExistingFile(name: string, pat: string, directory: Directory, type?: string, duration?: number, rawText?: string) { - const data = { size: 0, path: path.basename(pat), name, type: type ?? '' }; - const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration?.toString(), mime: '', toJson: () => undefined as any }) }; + function resolveExistingFile(name: string, pat: string, directory: Directory, type?: string | null, duration?: number, rawText?: string): Upload.FileResponse<Upload.FileInformation> { + const data = { size: 0, filepath: path.basename(pat), name, type: type ?? '', originalFilename: name, newFilename: name, mimetype: '', hashAlgorithm: 'md5' as 'md5' }; + const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), mime: '', toJson: () => undefined as any }) }; return { source: file, result: { accessPaths: { - agnostic: getAccessPaths(directory, data.path), + agnostic: getAccessPaths(directory, data.filepath), }, rawText, duration, @@ -144,8 +144,8 @@ export namespace DashUploadUtils { export function uploadYoutube(videoId: string, overwriteId: string): Promise<Upload.FileResponse> { return new Promise<Upload.FileResponse<Upload.FileInformation>>((res, rej) => { const name = videoId; - const path = name.replace(/^-/, '__') + '.mp4'; - const finalPath = serverPathToFile(Directory.videos, path); + const filepath = name.replace(/^-/, '__') + '.mp4'; + const finalPath = serverPathToFile(Directory.videos, filepath); if (existsSync(finalPath)) { uploadProgress.set(overwriteId, 'computing duration'); exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => { @@ -155,7 +155,7 @@ export namespace DashUploadUtils { }); } else { uploadProgress.set(overwriteId, 'starting download'); - const ytdlp = spawn(`yt-dlp`, ['-o', path, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']); + const ytdlp = spawn(`yt-dlp`, ['-o', filepath, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']); ytdlp.stdout.on('data', (data: any) => uploadProgress.set(overwriteId, data.toString())); @@ -170,20 +170,22 @@ export namespace DashUploadUtils { res({ source: { size: 0, - path, - name, - type: '', - toJSON: () => ({ name, path }), + filepath: name, + originalFilename: name, + newFilename: name, + mimetype: 'video', + hashAlgorithm: 'md5', + toJSON: () => ({ newFilename: name, filepath, mimetype: 'video', mtime: new Date(), size: 0, length: 0, originalFilename: name }), }, result: { name: 'failed youtube query', message: `Could not archive video. ${code ? errors : uploadProgress.get(videoId)}` }, }); } else { uploadProgress.set(overwriteId, 'computing duration'); - exec(`yt-dlp-o ${path} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => { + 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, path, name, type: 'video/mp4' }; - const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration.toString(), mime: '', toJson: () => undefined as any }) }; + 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)); }); } @@ -194,18 +196,18 @@ export namespace DashUploadUtils { export async function upload(file: File, overwriteGuid?: string): Promise<Upload.FileResponse> { const isAzureOn = usingAzure(); - const { type, path, name } = file; + const { mimetype: type, filepath, originalFilename } = file; const types = type?.split('/') ?? []; // uploadProgress.set(overwriteGuid ?? name, 'uploading'); // If the client sent a guid it uses to track upload progress, use that guid. Otherwise, use the file's name. const category = types[0]; let format = `.${types[1]}`; - console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`)); + console.log(green(`Processing upload of file (${originalFilename}) and format (${format}) with upload type (${type}) in category (${category}).`)); switch (category) { case 'image': if (imageFormats.includes(format)) { - const result = await UploadImage(path, basename(path)); + const result = await UploadImage(filepath, basename(filepath)); return { source: file, result }; } fs.unlink(path, () => {}); @@ -214,19 +216,19 @@ export namespace DashUploadUtils { if (format.includes('x-matroska')) { console.log('case video'); await new Promise(res => - ffmpeg(file.path) + ffmpeg(file.filepath) .videoCodec('copy') // this will copy the data instead of reencode it - .save(file.path.replace('.mkv', '.mp4')) + .save(file.filepath.replace('.mkv', '.mp4')) .on('end', res) .on('error', (e: any) => console.log(e)) ); - file.path = file.path.replace('.mkv', '.mp4'); + file.filepath = file.filepath.replace('.mkv', '.mp4'); format = '.mp4'; } if (format.includes('quicktime')) { let abort = false; await new Promise<void>(res => - ffmpeg.ffprobe(file.path, (err: any, metadata: any) => { + ffmpeg.ffprobe(file.filepath, (err: any, metadata: any) => { if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) { abort = true; } @@ -280,18 +282,18 @@ export namespace DashUploadUtils { } async function UploadPdf(file: File) { - const fileKey = (await md5File(file.path)) + '.pdf'; + const fileKey = (await md5File(file.filepath)) + '.pdf'; const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`; if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) { - fs.unlink(file.path, () => {}); + fs.unlink(file.filepath, () => {}); return new Promise<Upload.FileResponse>(res => { const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`; const readStream = createReadStream(serverPathToFile(Directory.text, textFilename)); var rawText = ''; - readStream.on('data', chunk => (rawText += chunk.toString())).on('end', () => res(resolveExistingFile(file.name, fileKey, Directory.pdfs, file.type, undefined, rawText))); + readStream.on('data', chunk => (rawText += chunk.toString())).on('end', () => res(resolveExistingFile(file.originalFilename ?? '', fileKey, Directory.pdfs, file.mimetype, undefined, rawText))); }); } - const dataBuffer = readFileSync(file.path); + const dataBuffer = readFileSync(file.filepath); const result: ParsedPDF | any = await parse(dataBuffer).catch((e: any) => e); if (!result.code) { await new Promise<void>((resolve, reject) => { @@ -300,11 +302,11 @@ export namespace DashUploadUtils { }); return MoveParsedFile(file, Directory.pdfs, undefined, result?.text, undefined, fileKey); } - return { source: file, result: { name: 'faile pdf pupload', message: `Could not upload (${file.name}).${result.message}` } }; + return { source: file, result: { name: 'faile pdf pupload', message: `Could not upload (${file.originalFilename}).${result.message}` } }; } async function UploadCsv(file: File) { - const { path: sourcePath } = file; + const { filepath: sourcePath } = file; // read the file as a string const data = readFileSync(sourcePath, 'utf8'); // split the string into an array of lines @@ -366,7 +368,7 @@ export namespace DashUploadUtils { } export interface ImageResizer { - width?: any; // sharp.Sharp; + width: number; suffix: SizeSuffix; } @@ -462,12 +464,12 @@ export namespace DashUploadUtils { * to appear in the new location */ export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number, targetName?: string): Promise<Upload.FileResponse> { - const { path: sourcePath } = file; - let name = targetName ?? path.basename(sourcePath); + const { filepath } = file; + let name = targetName ?? path.basename(filepath); suffix && (name += suffix); return new Promise(resolve => { const destinationPath = serverPathToFile(destination, name); - rename(sourcePath, destinationPath, error => { + rename(filepath, destinationPath, error => { resolve({ source: file, result: error @@ -622,7 +624,7 @@ export namespace DashUploadUtils { */ export function imageResampleSizes(ext: string): DashUploadUtils.ImageResizer[] { return [ - { suffix: SizeSuffix.Original }, + { suffix: SizeSuffix.Original, width: 0 }, ...[...(AcceptableMedia.imageFormats.includes(ext.toLowerCase()) ? Object.values(DashUploadUtils.Sizes) : [])].map(({ suffix, width }) => ({ width, suffix, diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts index c60880882..423c719c2 100644 --- a/src/server/GarbageCollector.ts +++ b/src/server/GarbageCollector.ts @@ -65,7 +65,7 @@ async function GarbageCollect(full: boolean = true) { // await new Promise(res => setTimeout(res, 3000)); const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users'); const users = await cursor.toArray(); - const ids: string[] = [...users.map(user => user.userDocumentId), ...users.map(user => user.sharingDocumentId), ...users.map(user => user.linkDatabaseId)]; + const ids: string[] = [...users.map((user:any) => user.userDocumentId), ...users.map((user:any) => user.sharingDocumentId), ...users.map((user:any) => user.linkDatabaseId)]; const visited = new Set<string>(); const files: { [name: string]: string[] } = {}; @@ -95,7 +95,7 @@ async function GarbageCollect(full: boolean = true) { const notToDelete = Array.from(visited); const toDeleteCursor = await Database.Instance.query({ _id: { $nin: notToDelete } }, { _id: 1 }); - const toDelete: string[] = (await toDeleteCursor.toArray()).map(doc => doc._id); + const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc:any) => doc._id); toDeleteCursor.close(); if (!full) { await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { "deleted": true } }); diff --git a/src/server/IDatabase.ts b/src/server/IDatabase.ts index dd4968579..2274792b3 100644 --- a/src/server/IDatabase.ts +++ b/src/server/IDatabase.ts @@ -3,13 +3,13 @@ import { Transferable } from './Message'; export const DocumentsCollection = 'documents'; export interface IDatabase { - update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): Promise<void>; - updateMany(query: any, update: any, collectionName?: string): Promise<mongodb.WriteOpResult>; + update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, upsert?: boolean, collectionName?: string): Promise<void>; + updateMany(query: any, update: any, collectionName?: string): Promise<mongodb.UpdateResult>; - replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): void; + replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, upsert?: boolean, collectionName?: string): void; - delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; - delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; + delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>; + delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>; dropSchema(...schemaNames: string[]): Promise<any>; @@ -20,5 +20,5 @@ export interface IDatabase { getCollectionNames(): Promise<string[]>; visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName?: string): Promise<void>; - query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise<mongodb.Cursor>; + query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise<mongodb.FindCursor>; } diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts index d2d8bb3b3..b74332bf5 100644 --- a/src/server/MemoryDatabase.ts +++ b/src/server/MemoryDatabase.ts @@ -19,7 +19,7 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve(Object.keys(this.db)); } - public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> { + public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> { const collection = this.getCollection(collectionName); const set = "$set"; if (set in value) { @@ -45,17 +45,17 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve(undefined); } - public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.WriteOpResult> { + public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.UpdateResult> { throw new Error("Can't updateMany a MemoryDatabase"); } - public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName = DocumentsCollection): void { + public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, upsert?: boolean, collectionName = DocumentsCollection): void { this.update(id, value, callback, upsert, collectionName); } - public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; - public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; - public delete(id: any, collectionName = DocumentsCollection): Promise<mongodb.DeleteWriteOpResultObject> { + public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>; + public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>; + public delete(id: any, collectionName = DocumentsCollection): Promise<mongodb.DeleteResult> { const i = id.id ?? id; delete this.getCollection(collectionName)[i]; @@ -105,7 +105,7 @@ export class MemoryDatabase implements IDatabase { } } - public query(): Promise<mongodb.Cursor> { + public query(): Promise<mongodb.FindCursor> { throw new Error("Can't query a MemoryDatabase"); } } diff --git a/src/server/ProcessFactory.ts b/src/server/ProcessFactory.ts index 63682368f..f69eda4c3 100644 --- a/src/server/ProcessFactory.ts +++ b/src/server/ProcessFactory.ts @@ -2,7 +2,7 @@ import { ChildProcess, spawn, StdioOptions } from "child_process"; import { existsSync, mkdirSync } from "fs"; import { Stream } from "stream"; import { fileDescriptorFromStream, pathFromRoot } from './ActionUtilities'; -import rimraf = require("rimraf"); +import { rimraf } from "rimraf"; export namespace ProcessFactory { @@ -13,7 +13,7 @@ export namespace ProcessFactory { const log_fd = await Logger.create(command, args); stdio = ["ignore", log_fd, log_fd]; } - const child = spawn(command, args, { detached, stdio }); + const child = spawn(command, args ?? [], { detached, stdio }); child.unref(); return child; } @@ -27,7 +27,7 @@ export namespace Logger { export async function initialize() { if (existsSync(logPath)) { if (!process.env.SPAWNED) { - await new Promise<any>(resolve => rimraf(logPath, resolve)); + await new Promise<any>(resolve => rimraf(logPath).then(resolve)); } } mkdirSync(logPath); diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 5683cd539..540bca776 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -1,6 +1,7 @@ import { cyan, green, red } from 'colors'; import { Express, Request, Response } from 'express'; import { AdminPriviliges } from '.'; +import { Utils } from '../Utils'; import { DashUserModel } from './authentication/DashUserModel'; import RouteSubscriber from './RouteSubscriber'; @@ -102,7 +103,7 @@ export default class RouteManager { let user = req.user as Partial<DashUserModel> | undefined; const { originalUrl: target } = req; if (process.env.DB === 'MEM' && !user) { - user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' }; + user = { id: 'guest', email: 'guest', userDocumentId: Utils.GuestID() }; } const core = { req, res, isRelease }; const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => { diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 4453b83bf..fc4656bdd 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -1,11 +1,11 @@ -import { google } from "googleapis"; -import { OAuth2Client, Credentials, OAuth2ClientOptions } from "google-auth-library"; -import { Opt } from "../../../fields/Doc"; -import { GaxiosResponse } from "gaxios"; -import request = require('request-promise'); -import * as qs from "query-string"; -import { Database } from "../../database"; -import { GoogleCredentialsLoader } from "./CredentialsLoader"; +import { google } from 'googleapis'; +import { OAuth2Client, Credentials, OAuth2ClientOptions } from 'google-auth-library'; +import { Opt } from '../../../fields/Doc'; +import { GaxiosResponse } from 'gaxios'; +import request from 'request-promise'; +import * as qs from 'query-string'; +import { Database } from '../../database'; +import { GoogleCredentialsLoader } from './CredentialsLoader'; /** * Scopes give Google users fine granularity of control @@ -13,33 +13,23 @@ import { GoogleCredentialsLoader } from "./CredentialsLoader"; * This is the somewhat overkill list of what Dash requests * from the user. */ -const scope = [ - 'documents.readonly', - 'documents', - 'presentations', - 'presentations.readonly', - 'drive', - 'drive.file', - 'photoslibrary', - 'photoslibrary.appendonly', - 'photoslibrary.sharing', - 'userinfo.profile' -].map(relative => `https://www.googleapis.com/auth/${relative}`); +const scope = ['documents.readonly', 'documents', 'presentations', 'presentations.readonly', 'drive', 'drive.file', 'photoslibrary', 'photoslibrary.appendonly', 'photoslibrary.sharing', 'userinfo.profile'].map( + relative => `https://www.googleapis.com/auth/${relative}` +); /** * This namespace manages server side authentication for Google API queries, either * from the standard v1 APIs or the Google Photos REST API. */ export namespace GoogleApiServerUtils { - /** * As we expand out to more Google APIs that are accessible from * the 'googleapis' module imported above, this enum will record * the list and provide a unified string representation of each API. */ export enum Service { - Documents = "Documents", - Slides = "Slides", + Documents = 'Documents', + Slides = 'Slides', } /** @@ -51,7 +41,7 @@ export namespace GoogleApiServerUtils { let oAuthOptions: OAuth2ClientOptions; /** - * This is a global authorization client that is never + * This is a global authorization client that is never * passed around, and whose credentials are never set. * Its job is purely to generate new authentication urls * (users will follow to get to Google's permissions GUI) @@ -64,7 +54,7 @@ export namespace GoogleApiServerUtils { * This function is called once before the server is started, * reading in Dash's project-specific credentials (client secret * and client id) for later repeated access. It also sets up the - * global, intentionally unauthenticated worker OAuth2 client instance. + * global, intentionally unauthenticated worker OAuth2 client instance. */ export function processProjectCredentials(): void { const { client_secret, client_id, redirect_uris } = GoogleCredentialsLoader.ProjectCredentials; @@ -72,7 +62,7 @@ export namespace GoogleApiServerUtils { oAuthOptions = { clientId: client_id, clientSecret: client_secret, - redirectUri: redirect_uris[0] + redirectUri: redirect_uris[0], }; worker = generateClient(); } @@ -98,7 +88,7 @@ export namespace GoogleApiServerUtils { * A literal union type indicating the valid actions for these 'googleapis' * requestions */ - export type Action = "create" | "retrieve" | "update"; + export type Action = 'create' | 'retrieve' | 'update'; /** * An interface defining any entity on which one can invoke @@ -135,7 +125,7 @@ export namespace GoogleApiServerUtils { return resolve(); } let routed: Opt<Endpoint>; - const parameters: any = { auth, version: "v1" }; + const parameters: any = { auth, version: 'v1' }; switch (sector) { case Service.Documents: routed = google.docs(parameters).documents; @@ -165,7 +155,7 @@ export namespace GoogleApiServerUtils { } let client = authenticationClients.get(userId); if (!client) { - authenticationClients.set(userId, client = generateClient(credentials)); + authenticationClients.set(userId, (client = generateClient(credentials))); } else if (refreshed) { client.setCredentials(credentials); } @@ -206,11 +196,11 @@ export namespace GoogleApiServerUtils { * with a Dash user in the googleAuthentication table of the database. * @param authenticationCode the Google-provided authentication code that the user copied * from Google's permissions UI and pasted into the overlay. - * + * * EXAMPLE CODE: 4/sgF2A5uGg4xASHf7VQDnLtdqo3mUlfQqLSce_HYz5qf1nFtHj9YTeGs - * + * * @returns the information necessary to authenticate a client side google photos request - * and display basic user information in the overlay on successful authentication. + * and display basic user information in the overlay on successful authentication. * This can be expanded as needed by adding properties to the interface GoogleAuthenticationResult. */ export async function processNewUser(userId: string, authenticationCode: string): Promise<EnrichedCredentials> { @@ -231,7 +221,7 @@ export namespace GoogleApiServerUtils { /** * This type represents the union of the full set of OAuth2 credentials * and all of a Google user's publically available information. This is the strucure - * of the JSON object we ultimately store in the googleAuthentication table of the database. + * of the JSON object we ultimately store in the googleAuthentication table of the database. */ export type EnrichedCredentials = Credentials & { userInfo: UserInfo }; @@ -259,14 +249,14 @@ export namespace GoogleApiServerUtils { * It's pretty cool: the credentials id_token is split into thirds by periods. * The middle third contains a base64-encoded JSON string with all the * user info contained in the interface below. So, we isolate that middle third, - * base64 decode with atob and parse the JSON. + * base64 decode with atob and parse the JSON. * @param credentials the client credentials returned from OAuth after the user * has executed the authentication routine * @returns the full set of credentials in the structure in which they'll be stored * in the database. */ function injectUserInfo(credentials: Credentials): EnrichedCredentials { - const userInfo: UserInfo = JSON.parse(atob(credentials.id_token!.split(".")[1])); + const userInfo: UserInfo = JSON.parse(atob(credentials.id_token!.split('.')[1])); return { ...credentials, userInfo }; } @@ -279,7 +269,7 @@ export namespace GoogleApiServerUtils { * @returns the credentials, or undefined if the user has no stored associated credentials, * and a flag indicating whether or not they were refreshed during retrieval */ - export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>, refreshed: boolean }> { + export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>; refreshed: boolean }> { let credentials = await Database.Auxiliary.GoogleAccessToken.Fetch(userId); let refreshed = false; if (!credentials) { @@ -299,7 +289,7 @@ export namespace GoogleApiServerUtils { * the Dash user id passed in. In addition to returning the credentials, it * writes the diff to the database. * @param credentials the credentials - * @param userId the id of the Dash user implicitly requesting that + * @param userId the id of the Dash user implicitly requesting that * his/her credentials be refreshed * @returns the updated credentials */ @@ -310,19 +300,18 @@ export namespace GoogleApiServerUtils { refreshToken: credentials.refresh_token, client_id, client_secret, - grant_type: "refresh_token" + grant_type: 'refresh_token', })}`; const { access_token, expires_in } = await new Promise<any>(async resolve => { const response = await request.post(url, headerParameters); resolve(JSON.parse(response)); }); // expires_in is in seconds, but we're building the new expiry date in milliseconds - const expiry_date = new Date().getTime() + (expires_in * 1000); + const expiry_date = new Date().getTime() + expires_in * 1000; await Database.Auxiliary.GoogleAccessToken.Update(userId, access_token, expiry_date); // update the relevant properties credentials.access_token = access_token; credentials.expiry_date = expiry_date; return credentials; } - -}
\ No newline at end of file +} diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts index 52d876e95..b1b84c300 100644 --- a/src/server/authentication/AuthenticationManager.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -3,12 +3,12 @@ import { Request, Response, NextFunction } from 'express'; import * as passport from 'passport'; import { IVerifyOptions } from 'passport-local'; import './Passport'; -import flash = require('express-flash'); import * as async from 'async'; import * as nodemailer from 'nodemailer'; -import c = require('crypto'); +import * as c from 'crypto'; import { emptyFunction, Utils } from '../../Utils'; import { MailOptions } from 'nodemailer/lib/stream-transport'; +import { check, validationResult } from 'express-validator'; /** * GET /signup @@ -31,14 +31,14 @@ export let getSignup = (req: Request, res: Response) => { */ export let postSignup = (req: Request, res: Response, next: NextFunction) => { const email = req.body.email as String; - req.assert('email', 'Email is not valid').isEmail(); - req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 }); - req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); - req.sanitize('email').normalizeEmail({ gmail_remove_dots: false }); + check('email', 'Email is not valid').isEmail().run(req); + check('password', 'Password must be at least 4 characters long').isLength({ min: 4 }).run(req); + check('confirmPassword', 'Passwords do not match').equals(req.body.password).run(req); + check('email').normalizeEmail({ gmail_remove_dots: false }).run(req); - const errors = req.validationErrors(); + const errors = validationResult(req).array(); - if (errors) { + if (errors.length) { return res.redirect('/signup'); } @@ -47,7 +47,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { const model = { email, password, - userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(), + userDocumentId: email === 'guest' ? Utils.GuestID() : Utils.GenerateGuid(), sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(), linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(), cacheDocumentIds: '', @@ -55,25 +55,21 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { const user = new User(model); - User.findOne({ email }, (err: any, existingUser: any) => { - if (err) { - return next(err); - } - if (existingUser) { - return res.redirect('/login'); - } - user.save((err: any) => { - if (err) { - return next(err); + User.findOne({ email }) + .then(existingUser => { + if (existingUser) { + return res.redirect('/login'); } - req.logIn(user, err => { - if (err) { - return next(err); - } - tryRedirectToTarget(req, res); - }); - }); - }); + user.save() + .then(() => { + req.logIn(user, err => { + if (err) return next(err); + tryRedirectToTarget(req, res); + }); + }) + .catch(err => next(err)); + }) + .catch(err => next(err)); }; const tryRedirectToTarget = (req: Request, res: Response) => { @@ -107,16 +103,18 @@ export let getLogin = (req: Request, res: Response) => { */ export let postLogin = (req: Request, res: Response, next: NextFunction) => { if (req.body.email === '') { - User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest()); + User.findOne({ email: 'guest' }) + .then(user => !user && initializeGuest()) + .catch(err => err); req.body.email = 'guest'; req.body.password = 'guest'; } else { - req.assert('email', 'Email is not valid').isEmail(); - req.assert('password', 'Password cannot be blank').notEmpty(); - req.sanitize('email').normalizeEmail({ gmail_remove_dots: false }); + check('email', 'Email is not valid').isEmail().run(req); + check('password', 'Password cannot be blank').notEmpty().run(req); + check('email').normalizeEmail({ gmail_remove_dots: false }).run(req); } - if (req.validationErrors()) { + if (validationResult(req).array().length) { req.flash('errors', 'Unable to login at this time. Please try again.'); return res.redirect('/signup'); } @@ -146,16 +144,10 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => { * and destroys the user's current session. */ export let getLogout = (req: Request, res: Response) => { - req.logout(emptyFunction); - const sess = req.session; - if (sess) { - sess.destroy(err => { - if (err) { - console.log(err); - } - }); - } - res.redirect('/login'); + req.logout(err => { + if (err) console.log(err); + else res.redirect('/login'); + }); }; export let getForgot = function (req: Request, res: Response) { @@ -179,7 +171,7 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio }); }, function (token: string, done: any) { - User.findOne({ email }, function (err: any, user: DashUserModel) { + User.findOne({ email }).then(user => { if (!user) { // NO ACCOUNT WITH SUBMITTED EMAIL res.redirect('/forgotPassword'); @@ -187,9 +179,7 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio } user.passwordResetToken = token; user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR - user.save(function (err: any) { - done(null, token, user); - }); + user.save().then(() => done(null, token, user)); }); }, function (token: Uint16Array, user: DashUserModel, done: any) { @@ -228,50 +218,43 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio }; export let getReset = function (req: Request, res: Response) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) { - if (!user || err) { - return res.redirect('/forgotPassword'); - } - res.render('reset.pug', { - title: 'Reset Password', - user: req.user, - }); - }); + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }) + .then(user => { + if (!user) return res.redirect('/forgotPassword'); + res.render('reset.pug', { + title: 'Reset Password', + user: req.user, + }); + }) + .catch(err => res.redirect('/forgotPassword')); }; export let postReset = function (req: Request, res: Response) { async.waterfall( [ function (done: any) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) { - if (!user || err) { - return res.redirect('back'); - } + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }) + .then(user => { + if (!user) return res.redirect('back'); - req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 }); - req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); + check('password', 'Password must be at least 4 characters long').isLength({ min: 4 }).run(req); + check('confirmPassword', 'Passwords do not match').equals(req.body.password).run(req); - if (req.validationErrors()) { - return res.redirect('back'); - } + if (validationResult(req).array().length) return res.redirect('back'); - user.password = req.body.password; - user.passwordResetToken = undefined; - user.passwordResetExpires = undefined; + user.password = req.body.password; + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; - user.save(function (err) { - if (err) { - res.redirect('/login'); - return; - } - req.logIn(user, function (err) { - if (err) { - return; - } - }); + user.save() + .then( + () => (req as any).logIn(user), + (err: any) => err + ) + .catch(err => res.redirect('/login')); done(null, user); - }); - }); + }) + .catch(err => res.redirect('back')); }, function (user: DashUserModel, done: any) { const smtpTransport = nodemailer.createTransport({ @@ -287,9 +270,8 @@ export let postReset = function (req: Request, res: Response) { subject: 'Your password has been changed', text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n', } as MailOptions; - smtpTransport.sendMail(mailOptions, function (err) { - done(null, err); - }); + + smtpTransport.sendMail(mailOptions, err => done(null, err)); }, ], function (err) { diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts index a1883beab..dbb7a79ed 100644 --- a/src/server/authentication/DashUserModel.ts +++ b/src/server/authentication/DashUserModel.ts @@ -2,6 +2,7 @@ import * as bcrypt from 'bcrypt-nodejs'; //@ts-ignore import * as mongoose from 'mongoose'; +import { Utils } from '../../Utils'; export type DashUserModel = mongoose.Document & { email: String; @@ -25,7 +26,7 @@ export type DashUserModel = mongoose.Document & { comparePassword: comparePasswordFunction; }; -type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; +type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => void) => void; export type AuthToken = { accessToken: string; @@ -63,7 +64,7 @@ const userSchema = new mongoose.Schema( * Password hash middleware. */ userSchema.pre('save', function save(next) { - const user = this as DashUserModel; + const user = this as any as DashUserModel; if (!user.isModified('password')) { return next(); } @@ -101,7 +102,7 @@ export function initializeGuest() { new User({ email: 'guest', password: 'guest', - userDocumentId: '__guest__', + userDocumentId: Utils.GuestID(), sharingDocumentId: '2', linkDatabaseId: '3', cacheDocumentIds: '', diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts index d7f891c34..a9cf6698b 100644 --- a/src/server/authentication/Passport.ts +++ b/src/server/authentication/Passport.ts @@ -1,6 +1,6 @@ import * as passport from 'passport'; import * as passportLocal from 'passport-local'; -import { default as User } from './DashUserModel'; +import { DashUserModel, default as User } from './DashUserModel'; const LocalStrategy = passportLocal.Strategy; @@ -9,21 +9,24 @@ passport.serializeUser<any, any>((req, user, done) => { }); passport.deserializeUser<any, any>((id, done) => { - User.findById(id, (err: any, user: any) => { - done(err, user); - }); + User.findById(id) + .exec() + .then(user => done(undefined, user)); }); // AUTHENTICATE JUST WITH EMAIL AND PASSWORD -passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => { - User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => { - if (error) return done(error); - if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email - user.comparePassword(password, (error: Error, isMatch: boolean) => { - if (error) return done(error); - if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password - // valid authentication HERE - return done(undefined, user); - }); - }); -}));
\ No newline at end of file +passport.use( + new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => { + User.findOne({ email: email.toLowerCase() }) + .then(user => { + if (!user) return done(undefined, false, { message: 'Invalid email or password' }); // invalid email + (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => { + if (error) return done(error); + if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password + // valid authentication HERE + return done(undefined, user); + }); + }) + .catch(error => done(error)); + }) +); diff --git a/src/server/database.ts b/src/server/database.ts index 725b66836..3a087ce38 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -9,8 +9,12 @@ import { Transferable } from './Message'; import { Upload } from './SharedMediaTypes'; export namespace Database { - export let disconnect: Function; + + class DocSchema implements mongodb.BSON.Document { + _id!: string; + id!: string; + } const schema = 'Dash'; const port = 27017; export const url = `mongodb://localhost:${port}/${schema}`; @@ -26,7 +30,7 @@ export namespace Database { export async function tryInitializeConnection() { try { const { connection } = mongoose; - disconnect = async () => new Promise<any>(resolve => connection.close(resolve)); + disconnect = async () => new Promise<any>(resolve => connection.close().then(resolve)); if (connection.readyState === ConnectionStates.disconnected) { await new Promise<void>((resolve, reject) => { connection.on('error', reject); @@ -35,12 +39,11 @@ export namespace Database { resolve(); }); mongoose.connect(url, { - useNewUrlParser: true, - useUnifiedTopology: true, + //useNewUrlParser: true, dbName: schema, // reconnectTries: Number.MAX_VALUE, // reconnectInterval: 1000, - }); + }); }); } } catch (e) { @@ -60,11 +63,10 @@ export namespace Database { async doConnect() { console.error(`\nConnecting to Mongo with URL : ${url}\n`); return new Promise<void>(resolve => { - this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => { - console.error("mongo connect response\n"); + this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000 }).then(client => { + console.error('mongo connect response\n'); if (!client) { - console.error("\nMongo connect failed with the error:\n"); - console.log(_err); + console.error('\nMongo connect failed with the error:\n'); process.exit(0); } this.db = client.db(); @@ -74,20 +76,24 @@ export namespace Database { }); } - public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) { + public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, upsert = true, collectionName = DocumentsCollection) { if (this.db) { - const collection = this.db.collection(collectionName); + const collection = this.db.collection<DocSchema>(collectionName); const prom = this.currentWrites[id]; let newProm: Promise<void>; const run = (): Promise<void> => { return new Promise<void>(resolve => { - collection.updateOne({ _id: id }, value, { upsert } - , (err, res) => { + collection + .updateOne({ _id: id }, value, { upsert }) + .then(res => { if (this.currentWrites[id] === newProm) { delete this.currentWrites[id]; } resolve(); - callback(err, res); + callback(undefined as any, res); + }) + .catch(error => { + console.log('MOngo UPDATE ONE ERROR:', error); }); }); }; @@ -99,21 +105,20 @@ export namespace Database { } } - public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) { + public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult<mongodb.Document>) => void, upsert = true, collectionName = DocumentsCollection) { if (this.db) { - const collection = this.db.collection(collectionName); + const collection = this.db.collection<DocSchema>(collectionName); const prom = this.currentWrites[id]; let newProm: Promise<void>; const run = (): Promise<void> => { return new Promise<void>(resolve => { - collection.replaceOne({ _id: id }, value, { upsert } - , (err, res) => { - if (this.currentWrites[id] === newProm) { - delete this.currentWrites[id]; - } - resolve(); - callback(err, res); - }); + collection.replaceOne({ _id: id }, value, { upsert }).then(res => { + if (this.currentWrites[id] === newProm) { + delete this.currentWrites[id]; + } + resolve(); + callback(undefined as any, res as any); + }); }); }; newProm = prom ? prom.then(run) : run(); @@ -135,15 +140,20 @@ export namespace Database { return collectionNames; } - public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; - public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; + public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>; + public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>; public delete(id: any, collectionName = DocumentsCollection) { - if (typeof id === "string") { + if (typeof id === 'string') { id = { _id: id }; } if (this.db) { const db = this.db; - return new Promise(res => db.collection(collectionName).deleteMany(id, (err, result) => res(result))); + return new Promise(res => + db + .collection(collectionName) + .deleteMany(id) + .then(result => res(result)) + ); } else { return new Promise(res => this.onConnect.push(() => res(this.delete(id, collectionName)))); } @@ -170,22 +180,27 @@ export namespace Database { public async insert(value: any, collectionName = DocumentsCollection) { if (this.db && value !== null) { - if ("id" in value) { + if ('id' in value) { value._id = value.id; delete value.id; } const id = value._id; - const collection = this.db.collection(collectionName); + const collection = this.db.collection<DocSchema>(collectionName); const prom = this.currentWrites[id]; let newProm: Promise<void>; const run = (): Promise<void> => { return new Promise<void>(resolve => { - collection.insertOne(value, (err, res) => { - if (this.currentWrites[id] === newProm) { - delete this.currentWrites[id]; - } - resolve(); - }); + collection + .insertOne(value) + .then(res => { + if (this.currentWrites[id] === newProm) { + delete this.currentWrites[id]; + } + resolve(); + }) + .catch(err => { + console.log('Mongo INSERT ERROR: ', err); + }); }); }; newProm = prom ? prom.then(run) : run(); @@ -198,11 +213,12 @@ export namespace Database { public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) { if (this.db) { - this.db.collection(collectionName).findOne({ _id: id }, (err, result) => { + const collection = this.db.collection<DocSchema>(collectionName); + collection.findOne({ _id: id }).then(result => { if (result) { result.id = result._id; - delete result._id; - fn(result); + //delete result._id; + fn(result as any); } else { fn(undefined); } @@ -212,19 +228,19 @@ export namespace Database { } } - public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) { + public async getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) { if (this.db) { - this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => { - if (err) { - console.log(err.message); - console.log(err.errmsg); - } - fn(docs.map(doc => { + const found = await this.db + .collection<DocSchema>(collectionName) + .find({ _id: { $in: ids } }) + .toArray(); + fn( + found.map((doc: any) => { doc.id = doc._id; delete doc._id; return doc; - })); - }); + }) + ); } else { this.onConnect.push(() => this.getDocuments(ids, fn, collectionName)); } @@ -257,15 +273,15 @@ export namespace Database { } } - public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.Cursor> { + public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.FindCursor> { if (this.db) { - let cursor = this.db.collection(collectionName).find(query); + let cursor = this.db.collection<DocSchema>(collectionName).find(query); if (projection) { cursor = cursor.project(projection); } - return Promise.resolve<mongodb.Cursor>(cursor); + return Promise.resolve<mongodb.FindCursor>(cursor); } else { - return new Promise<mongodb.Cursor>(res => { + return new Promise<mongodb.FindCursor>(res => { this.onConnect.push(() => res(this.query(query, projection, collectionName))); }); } @@ -274,22 +290,36 @@ export namespace Database { public updateMany(query: any, update: any, collectionName = DocumentsCollection) { if (this.db) { const db = this.db; - return new Promise<mongodb.WriteOpResult>(res => db.collection(collectionName).update(query, update, (_, result) => res(result))); + return new Promise<mongodb.UpdateResult>(res => + db + .collection(collectionName) + .updateMany(query, update) + .then(result => res(result)) + .catch(error => { + console.log('Mongo INSERT MANY ERROR:', error); + }) + ); } else { - return new Promise<mongodb.WriteOpResult>(res => { - this.onConnect.push(() => this.updateMany(query, update, collectionName).then(res)); + return new Promise<mongodb.UpdateResult>(res => { + this.onConnect.push(() => + this.updateMany(query, update, collectionName) + .then(res) + .catch(error => { + console.log('Mongo UPDATAE MANY ERROR: ', error); + }) + ); }); } } public print() { - console.log("db says hi!"); + console.log('db says hi!'); } } function getDatabase() { switch (process.env.DB) { - case "MEM": + case 'MEM': return new MemoryDatabase(); default: return new Database(); @@ -304,13 +334,12 @@ export namespace Database { * or Dash-internal user data. */ export namespace Auxiliary { - /** * All the auxiliary MongoDB collections (schemas) */ export enum AuxiliaryCollections { - GooglePhotosUploadHistory = "uploadedFromGooglePhotos", - GoogleAccess = "googleAuthentication", + GooglePhotosUploadHistory = 'uploadedFromGooglePhotos', + GoogleAccess = 'googleAuthentication', } /** @@ -322,16 +351,18 @@ export namespace Database { const cursor = await Instance.query(query, undefined, collection); const results = await cursor.toArray(); const slice = results.slice(0, Math.min(cap, results.length)); - return removeId ? slice.map(result => { - delete result._id; - return result; - }) : slice; + return removeId + ? slice.map((result: any) => { + delete result._id; + return result; + }) + : slice; }; /** * Searches for the @param query in the specified @param collection, * and returns at most the first result. If @param removeId is true, - * as it is by default, each object will be stripped of its database id. + * as it is by default, each object will be stripped of its database id. * Worth the special case since it converts the Array return type to a single * object of the specified type. */ @@ -341,7 +372,7 @@ export namespace Database { }; /** - * Checks to see if an image with the given @param contentSize + * Checks to see if an image with the given @param contentSize * already exists in the aux database, i.e. has already been downloaded from Google Photos. */ export const QueryUploadHistory = async (contentSize: number) => { @@ -355,7 +386,7 @@ export namespace Database { export const LogUpload = async (information: Upload.ImageInformation) => { const bundle = { _id: Utils.GenerateDeterministicGuid(String(information.contentSize)), - ...information + ...information, }; return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); }; @@ -365,7 +396,6 @@ export namespace Database { * facilitates interactions with all their APIs for a given account. */ export namespace GoogleAccessToken { - /** * Format stored in database. */ @@ -373,7 +403,7 @@ export namespace Database { /** * Retrieves the credentials associaed with @param userId - * and optionally removes their database id according to @param removeId. + * and optionally removes their database id according to @param removeId. */ export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => { return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId); @@ -381,7 +411,7 @@ export namespace Database { /** * Writes the @param enrichedCredentials to the database, associated - * with @param userId for later retrieval and updating. + * with @param userId for later retrieval and updating. */ export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => { return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess); @@ -400,7 +430,7 @@ export namespace Database { }; /** - * Revokes the credentials associated with @param userId. + * Revokes the credentials associated with @param userId. */ export const Revoke = async (userId: string) => { const entry = await Fetch(userId, false); @@ -408,9 +438,6 @@ export namespace Database { Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess); } }; - } - } - } diff --git a/src/server/index.ts b/src/server/index.ts index 8b2e18847..745653a19 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,4 +1,4 @@ -require('dotenv').config(); +import * as dotenv from 'dotenv'; import { yellow } from 'colors'; import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; @@ -26,7 +26,7 @@ import { Logger } from './ProcessFactory'; import RouteManager, { Method, PublicHandler } from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; import initializeServer, { resolvedPorts } from './server_Initialization'; - +dotenv.config(); export const AdminPriviliges: Map<string, boolean> = new Map(); export const onWindows = process.platform === 'win32'; export let sessionAgent: AppliedSessionAgent; diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index ccb709453..afc6231e5 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -1,13 +1,9 @@ import * as bodyParser from 'body-parser'; import { blue, yellow } from 'colors'; -import * as cookieParser from 'cookie-parser'; import * as cors from 'cors'; import * as express from 'express'; import * as session from 'express-session'; -import * as expressValidator from 'express-validator'; -import * as fs from 'fs'; -import { Server as HttpServer } from 'http'; -import { createServer, Server as HttpsServer } from 'https'; +import { createServer } from 'https'; import * as passport from 'passport'; import * as request from 'request'; import * as webpack from 'webpack'; @@ -22,12 +18,11 @@ import { Database } from './database'; import RouteManager from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; import { WebSocket } from './websocket'; -import brotli = require('brotli'); -import expressFlash = require('express-flash'); -import flash = require('connect-flash'); -const MongoStore = require('connect-mongo')(session); -const config = require('../../webpack.config'); -const compiler = webpack(config); +import * as expressFlash from 'express-flash'; +import * as flash from 'connect-flash'; +import * as brotli from 'brotli'; +import * as MongoStoreConnect from 'connect-mongo'; +import * as config from '../../webpack.config'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ @@ -40,32 +35,23 @@ export let resolvedServerUrl: string; export default async function InitializeServer(routeSetter: RouteSetter) { const isRelease = determineEnvironment(); const app = buildWithMiddleware(express()); - - const compiler = webpack(config); - - app.use( - require('webpack-dev-middleware')(compiler, { - publicPath: config.output.publicPath, - }) - ); - - app.use(require('webpack-hot-middleware')(compiler)); + const compiler = webpack(config as any); // route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request + app.use(wdm(compiler, { publicPath: config.output.publicPath })); + app.use(whm(compiler)); app.get(new RegExp(/^\/+$/), (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); //all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc) app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) })); - app.use(wdm(compiler, { publicPath: config.output.publicPath })); - app.use(whm(compiler)); registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc) registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies isRelease && !SSL.Loaded && SSL.exit(); routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc) registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content - let server: HttpServer | HttpsServer; isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort)); - await new Promise<void>(resolve => (server = isRelease ? createServer(SSL.Credentials, app).listen(resolvedPorts.server, resolve) : app.listen(resolvedPorts.server, resolve))); + const server = isRelease ? createServer(SSL.Credentials, app) : app; + await new Promise<void>(resolve => server.listen(resolvedPorts.server, resolve)); logPort('server', resolvedPorts.server); resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`; @@ -80,26 +66,26 @@ export default async function InitializeServer(routeSetter: RouteSetter) { const week = 7 * 24 * 60 * 60 * 1000; const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc'; +const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url }); function buildWithMiddleware(server: express.Express) { [ - cookieParser(), session({ secret, - resave: true, + resave: false, cookie: { maxAge: week }, saveUninitialized: true, - store: process.env.DB === 'MEM' ? new session.MemoryStore() : new MongoStore({ url: Database.url }), + store, }), flash(), expressFlash(), bodyParser.json({ limit: '10mb' }), bodyParser.urlencoded({ extended: true }), - expressValidator(), passport.initialize(), passport.session(), (req: express.Request, res: express.Response, next: express.NextFunction) => { res.locals.user = req.user; + // console.log('HEADER:' + req.originalUrl + ' path = ' + req.path); if ((req.originalUrl.endsWith('.png') || req.originalUrl.endsWith('.jpg') || (process.env.RELEASE === 'true' && req.originalUrl.endsWith('.js'))) && req.method === 'GET') { const period = 30000; res.set('Cache-control', `public, max-age=${period}`); @@ -110,6 +96,7 @@ function buildWithMiddleware(server: express.Express) { next(); }, ].forEach(next => server.use(next)); + return server; } diff --git a/src/server/websocket.ts b/src/server/websocket.ts index be5cdb202..a26b81bdf 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -1,10 +1,8 @@ import { blue } from 'colors'; import * as express from 'express'; -import { createServer, Server } from 'https'; +import { createServer } from 'https'; +import { Server, Socket } from '../../node_modules/socket.io/dist/index'; import { networkInterfaces } from 'os'; -import * as sio from 'socket.io'; -import { Socket } from 'socket.io'; -import { Opt } from '../fields/Doc'; import { Utils } from '../Utils'; import { logPort } from './ActionUtilities'; import { timeMap } from './ApiManagers/UserManager'; @@ -18,31 +16,31 @@ import { DocumentsCollection } from './IDatabase'; import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message'; import { Search } from './Search'; import { resolvedPorts } from './server_Initialization'; -var _ = require('lodash'); +import * as _ from 'lodash'; export namespace WebSocket { export let _socket: Socket; export const clients: { [key: string]: Client } = {}; - export const socketMap = new Map<SocketIO.Socket, string>(); + export const socketMap = new Map<Socket, string>(); export const userOperations = new Map<string, number>(); export let disconnect: Function; export async function initialize(isRelease: boolean, app: express.Express) { - let io: sio.Server; + let io: Server; if (isRelease) { const { socketPort } = process.env; if (socketPort) { resolvedPorts.socket = Number(socketPort); } - let socketEndpoint: Opt<Server>; - await new Promise<void>(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve))); - io = sio(socketEndpoint!, SSL.Credentials as any); + io = new Server(createServer(SSL.Credentials, app), SSL.Credentials as any); + io.listen(resolvedPorts.socket); } else { - io = sio().listen(resolvedPorts.socket); + io = new Server(); + io.listen(resolvedPorts.socket); } logPort('websocket', resolvedPorts.socket); - io.on('connection', function (socket: Socket) { + io.on('connection', socket => { _socket = socket; socket.use((_packet, next) => { const userEmail = socketMap.get(socket); @@ -67,8 +65,8 @@ export namespace WebSocket { socket.on('create or join', function (room) { console.log('Received request to create or join room ' + room); - const clientsInRoom = socket.adapter.rooms[room]; - const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; + const clientsInRoom = socket.rooms.has(room); + const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0; console.log('Room ' + room + ' now has ' + numClients + ' client(s)'); if (numClients === 0) { @@ -90,7 +88,7 @@ export namespace WebSocket { socket.on('ipaddr', function () { const ifaces = networkInterfaces(); for (const dev in ifaces) { - ifaces[dev].forEach(function (details) { + ifaces[dev]?.forEach(function (details) { if (details.family === 'IPv4' && details.address !== '127.0.0.1') { socket.emit('ipaddr', details.address); } @@ -191,7 +189,7 @@ export namespace WebSocket { initializeGuest(); } - function barReceived(socket: SocketIO.Socket, userEmail: string) { + function barReceived(socket: Socket, userEmail: string) { clients[userEmail] = new Client(userEmail.toString()); const currentdate = new Date(); const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds(); @@ -308,9 +306,9 @@ export namespace WebSocket { if (sendBack) { console.log('Warning: list modified during update. Composite list is being returned.'); const id = socket.id; - socket.id = ''; + (socket as any).id = ''; socket.broadcast.emit(MessageStore.UpdateField.Message, diff); - socket.id = id; + (socket as any).id = id; } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff); dispatchNextOp(diff.id); }, @@ -401,9 +399,9 @@ export namespace WebSocket { // the two copies are different, so the server sends its copy. console.log('SEND BACK'); const id = socket.id; - socket.id = ''; + (socket as any).id = ''; socket.broadcast.emit(MessageStore.UpdateField.Message, diff); - socket.id = id; + (socket as any).id = id; } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff); dispatchNextOp(diff.id); }, diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 23e4680fd..284aae338 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -12,7 +12,13 @@ declare module 'iink-js'; declare module 'reveal'; declare module 'react-reveal'; declare module 'react-reveal/makeCarousel'; -declare module 'react-resizable-rotatable-draggable'; +declare module '@hig/flyout'; +declare module 'express-flash'; +declare module 'connect-flash' { + interface flash {} +} +declare module 'connect-mongo'; +declare module '@mui/material'; declare module '@react-pdf/renderer' { import * as React from 'react'; |