diff options
author | bobzel <zzzman@gmail.com> | 2023-12-05 20:29:53 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-12-05 20:29:53 -0500 |
commit | b80d27912cd6d8bc4fe039e52d16582bfbe72c74 (patch) | |
tree | e088386dc4e4d974fbb52d74054ec5aa937a8ef0 /src | |
parent | 2bd239e39264a362d1fbb013ce2613d03247d78e (diff) |
mostly working version with latest libraries
Diffstat (limited to 'src')
30 files changed, 706 insertions, 673 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 38a59d524..08ddfa817 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,6 +1,8 @@ import * as v4 from 'uuid/v4'; import * as v5 from 'uuid/v5'; 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 { DocumentType } from './client/documents/DocumentTypes'; import { Colors } from './client/views/global/globalEnums'; @@ -54,6 +56,14 @@ export namespace Utils { return v5(seed, v5.URL); } + export function GenerateMongoId(id: string): string { + return id.length !== 36 ? Utils.GenerateDeterministicGuid(id) : id; + } + + export function GuestID() { + return '__guest__'; + } + /** * Uploads an image buffer to the server and stores with specified filename. by default the image * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large) @@ -397,14 +407,14 @@ export namespace Utils { }; } - export function Emit<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T) { + export function Emit<T>(socket: { emit: (msg: string, args: T) => void }, message: Message<T>, args: T) { log('Emit', message.Name, args, false); socket.emit(message.Message, args); } - export function EmitCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>; - export function EmitCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void; - export function EmitCallback<T>(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)); @@ -413,20 +423,20 @@ export namespace Utils { } } - export function AddServerHandler<T>(socket: SocketIOClient.Socket, message: Message<T>, handler: (args: T) => any) { + export function AddServerHandler<T>(socket: { on: (event: string, cb: (args: any) => void) => void }, message: Message<T>, handler: (args: T) => any) { socket.on(message.Message, loggingCallback('Incoming', handler, message.Name)); } - export function AddServerHandlerCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) { + export function AddServerHandlerCallback<T>(socket: { on: (event: string, cb: (arg: T, fn: (res: any) => any) => void) => void }, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) { socket.on(message.Message, (arg: T, fn: (res: any) => any) => { log('S receiving', message.Name, arg, true); handler([arg, loggingCallback('S sending', fn, message.Name)]); }); } - export type RoomHandler = (socket: SocketIOClient.Socket, room: string) => any; - export type UsedSockets = SocketIOClient.Socket; + export type RoomHandler = (socket: Socket, room: string) => any; + export type UsedSockets = Socket; export type RoomMessage = 'create or join' | 'created' | 'joined'; - export function AddRoomHandler(socket: SocketIOClient.Socket, message: RoomMessage, handler: RoomHandler) { + export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) { 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/documents/Documents.ts b/src/client/documents/Documents.ts index df81e12f0..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]; }; @@ -1918,7 +1919,7 @@ export namespace DocUtils { overwriteDoc.loadingError = (result as any).message; LoadingBox.removeCurrentlyLoading(overwriteDoc); } - } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype??"", result, options, overwriteDoc); + } else newFilename && 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/Scripting.ts b/src/client/util/Scripting.ts index 400b63a1c..b3c694024 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -120,6 +120,35 @@ class ScriptingCompilerHost { } return undefined; } + resolveModuleNames(moduleNames: string[], containingFile: string): any { + const resolvedModules: ts.ResolvedModule[] = []; + for (const moduleName of moduleNames) { + // try to use standard resolution + let result = ts.resolveModuleName( + moduleName, + containingFile, + {}, + { + fileExists: (fileName: string) => true as any, + readFile: (fileName: string) => 'true', + } + ); + if (result.resolvedModule) { + resolvedModules.push(result.resolvedModule); + } else { + // check fallback locations, for simplicity assume that module at location + // should be represented by '.d.ts' file + // for (const location of moduleSearchLocations) { + // const modulePath = path.join(location, moduleName + ".d.ts"); + // if (fileExists(modulePath)) { + // resolvedModules.push({ resolvedFileName: modulePath }); + // } + // } + } + } + return resolvedModules; + } + // getDefaultLibFileName(options: ts.CompilerOptions): string { getDefaultLibFileName(options: any): string { return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means... @@ -247,7 +276,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/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/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 13df352e3..41a2507f9 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -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/MainView.tsx b/src/client/views/MainView.tsx index 53182497c..1408e3124 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -43,7 +43,6 @@ 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'; @@ -71,6 +70,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 +1065,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/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/TreeView.tsx b/src/client/views/collections/TreeView.tsx index b5666b917..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'; @@ -36,6 +35,8 @@ import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; 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/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/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index d10cbac5e..1b5161e47 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -13,8 +13,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; import * as React from 'react'; -import globalCssVariables = require('../global/globalCssVariables.scss'); - +// 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/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 525ff7ec0..d441dafec 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,7 +816,7 @@ export namespace Doc { } export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) { - if (infield instanceof List<any>) { + if (!(infield instanceof Doc)) { infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system)); return; } @@ -1201,14 +1201,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/ScriptField.ts b/src/fields/ScriptField.ts index cd07a8885..03dc71c48 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -45,12 +45,12 @@ const scriptSchema = createSimpleSchema({ function finalizeScript(script: ScriptField) { const comp = CompileScript(script.script.originalScript, script.script.options); if (!comp.compiled) { - throw new Error("Couldn't compile loaded script"); + // throw new Error("Couldn't compile loaded script"); } if (script.setterscript) { const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options); if (!compset.compiled) { - throw new Error("Couldn't compile setter script"); + // throw new Error("Couldn't compile setter script"); } (script as any).setterscript = compset; } @@ -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 fd119e0bf..ee212e4af 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -5,7 +5,6 @@ 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'; @@ -15,7 +14,8 @@ import { Utils } from '../Utils'; import './ImageUpload.scss'; import { MobileInterface } from './MobileInterface'; 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) } diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index 56d8aff60..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 } from "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/UserManager.ts b/src/server/ApiManagers/UserManager.ts index 9252202b0..0d36ee957 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -7,6 +7,7 @@ import { Opt } from '../../fields/Doc'; import { WebSocket } from '../websocket'; import { resolvedPorts } from '../server_Initialization'; import { DashVersion } from '../../fields/DocSymbols'; +import { Utils } from '../../Utils'; export const timeMap: { [id: string]: number } = {}; interface ActivityUnit { @@ -49,7 +50,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 +82,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({ diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 643626ae9..2bea15915 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -200,7 +200,7 @@ export namespace DashUploadUtils { 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': 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/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts index 74d8d2523..5bc6e96b4 100644 --- a/src/server/authentication/AuthenticationManager.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -46,7 +46,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: '', @@ -54,25 +54,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().then(undefined, (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) => { @@ -106,7 +102,9 @@ 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 { @@ -146,14 +144,7 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => { */ export let getLogout = (req: Request, res: Response) => { req.logout(emptyFunction); - const sess = req.session; - if (sess) { - sess.destroy(err => { - if (err) { - console.log(err); - } - }); - } + req.session?.destroy(err => err && console.log(err)); res.redirect('/login'); }; @@ -178,7 +169,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'); @@ -186,9 +177,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().then(undefined, (err: any) => { - done(null, token, user); - }); + user.save().then(() => done(null, token, user)); }); }, function (token: Uint16Array, user: DashUserModel, done: any) { @@ -227,50 +216,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); + 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); - if (req.validationErrors()) { - return res.redirect('back'); - } + if (req.validationErrors()) 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().then(undefined, (err:any) => { - 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({ @@ -286,9 +268,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 37bc00a85..0893bfd35 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -7,11 +7,14 @@ import { DocumentsCollection, IDatabase } from './IDatabase'; import { MemoryDatabase } from './MemoryDatabase'; import { Transferable } from './Message'; import { Upload } from './SharedMediaTypes'; -import { ObjectId } from 'mongodb'; 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}`; @@ -36,11 +39,11 @@ export namespace Database { resolve(); }); mongoose.connect(url, { - //useNewUrlParser: true, + //useNewUrlParser: true, dbName: schema, // reconnectTries: Number.MAX_VALUE, // reconnectInterval: 1000, - }); + }); }); } } catch (e) { @@ -60,10 +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, }).then(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.error('\nMongo connect failed with the error:\n'); process.exit(0); } this.db = client.db(); @@ -75,18 +78,18 @@ export namespace Database { 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: new ObjectId(id) }, value, { upsert }).then(res => { - if (this.currentWrites[id] === newProm) { - delete this.currentWrites[id]; - } - resolve(); - callback(undefined as any, res); - }); + collection.updateOne({ _id: id }, value, { upsert }).then(res => { + if (this.currentWrites[id] === newProm) { + delete this.currentWrites[id]; + } + resolve(); + callback(undefined as any, res); + }); }); }; newProm = prom ? prom.then(run) : run(); @@ -99,18 +102,18 @@ export namespace Database { 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: new ObjectId(id)}, value, { upsert }).then( res => { - if (this.currentWrites[id] === newProm) { - delete this.currentWrites[id]; - } - resolve(); - callback(undefined as any, res as any); - }); + 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,12 +138,17 @@ export namespace Database { 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") { - id = { _id: new ObjectId(id) }; + if (typeof id === 'string') { + id = { _id: id }; } if (this.db) { const db = this.db; - return new Promise(res => db.collection(collectionName).deleteMany(id).then(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)))); } @@ -167,12 +175,12 @@ 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> => { @@ -195,11 +203,12 @@ export namespace Database { public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) { if (this.db) { - this.db.collection(collectionName).findOne({ _id: new ObjectId(id) }).then(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.id); + fn(result as any); } else { fn(undefined); } @@ -209,19 +218,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.map(id => new ObjectId(id)) } }).map(docs => { - // if (err) { - // console.log(err.message); - // console.log(err.errmsg); - // } - fn(docs.map((doc:any) => { + 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)); } @@ -256,7 +265,7 @@ export namespace Database { 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); } @@ -271,7 +280,12 @@ export namespace Database { public updateMany(query: any, update: any, collectionName = DocumentsCollection) { if (this.db) { const db = this.db; - return new Promise<mongodb.UpdateResult>(res => db.collection(collectionName).updateMany(query, update).then(result => res(result))); + return new Promise<mongodb.UpdateResult>(res => + db + .collection(collectionName) + .updateMany(query, update) + .then(result => res(result)) + ); } else { return new Promise<mongodb.UpdateResult>(res => { this.onConnect.push(() => this.updateMany(query, update, collectionName).then(res)); @@ -280,13 +294,13 @@ export namespace Database { } 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(); @@ -301,13 +315,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', } /** @@ -319,16 +332,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:any) => { - 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. */ @@ -338,7 +353,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) => { @@ -352,7 +367,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); }; @@ -362,7 +377,6 @@ export namespace Database { * facilitates interactions with all their APIs for a given account. */ export namespace GoogleAccessToken { - /** * Format stored in database. */ @@ -370,7 +384,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); @@ -378,7 +392,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); @@ -397,7 +411,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); @@ -405,9 +419,6 @@ export namespace Database { Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess); } }; - } - } - } diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index af8b8dfdd..a4deaa744 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -1,13 +1,10 @@ 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,10 +19,10 @@ import { Database } from './database'; import RouteManager from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; import { WebSocket } from './websocket'; -import * as brotli from 'brotli'; import * as expressFlash from 'express-flash'; import * as flash from 'connect-flash'; -import * as MongoStoreConnect from 'connect-mongo'; +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 @@ -41,29 +38,21 @@ export default async function InitializeServer(routeSetter: RouteSetter) { const app = buildWithMiddleware(express()); const compiler = webpack(config as any); - app.use( - require('webpack-dev-middleware')(compiler, { - publicPath: config.output.publicPath, - }) - ); - - app.use(require('webpack-hot-middleware')(compiler)); - // 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}`; @@ -78,26 +67,27 @@ export default async function InitializeServer(routeSetter: RouteSetter) { const week = 7 * 24 * 60 * 60 * 1000; const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc'; +const store = process.env.DB === 'MEM' || true ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url }); function buildWithMiddleware(server: express.Express) { [ - cookieParser(), session({ secret, resave: true, cookie: { maxAge: week }, saveUninitialized: true, - store: process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url }), + store, }), flash(), expressFlash(), bodyParser.json({ limit: '10mb' }), bodyParser.urlencoded({ extended: true }), - expressValidator.body, + expressValidator(), // adds functions (e.g., assert()) to 'req' that help validate the request in the route handling methods 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}`); @@ -108,7 +98,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 4453001b0..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 * as _socket from 'socket.io'; -import { Opt } from '../fields/Doc'; import { Utils } from '../Utils'; import { logPort } from './ActionUtilities'; import { timeMap } from './ApiManagers/UserManager'; @@ -18,32 +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'); -type Socket = typeof _socket; +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); @@ -69,7 +66,7 @@ export namespace WebSocket { console.log('Received request to create or join room ' + room); const clientsInRoom = socket.rooms.has(room); - const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; + const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0; console.log('Room ' + room + ' now has ' + numClients + ' client(s)'); if (numClients === 0) { @@ -192,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(); @@ -309,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); }, @@ -402,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 884e55fc7..6a1afbb03 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -13,7 +13,6 @@ declare module 'reveal'; declare module 'react-reveal'; declare module 'react-reveal/makeCarousel'; declare module 'react-resizable-rotatable-draggable'; -declare module 'socket.io'; declare module '@hig/flyout'; declare module 'uuid/v4'; declare module 'uuid/v5'; |