From e0316c21838613df0fbf43df6a9ca5d696c52f47 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 30 Jul 2019 20:19:58 -0400 Subject: more interesting speech commands and command manager pattern for DictationManager --- src/client/util/DictationManager.ts | 88 +++++++++++++++++++++ src/client/views/GlobalKeyHandler.ts | 8 +- src/client/views/MainView.tsx | 2 + .../views/collections/CollectionBaseView.tsx | 18 +++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 92 +++++++++++++--------- src/client/views/nodes/DocumentView.tsx | 48 ++++++++++- 6 files changed, 216 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b58bdb6c7..60b25afc5 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,3 +1,10 @@ +import { string } from "prop-types"; +import { observable, action } from "mobx"; +import { SelectionManager } from "./SelectionManager"; +import { DocumentView } from "../views/nodes/DocumentView"; +import { UndoManager } from "./UndoManager"; +import * as converter from "words-to-numbers"; + namespace CORE { export interface IWindow extends Window { webkitSpeechRecognition: any; @@ -5,9 +12,14 @@ namespace CORE { } const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; +export type Action = (target: DocumentView) => any | Promise; +export type DynamicAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; +export type RegexEntry = { key: RegExp, value: DynamicAction }; export default class DictationManager { public static Instance = new DictationManager(); + private registeredCommands = new Map(); + private registeredRegexes: RegexEntry[] = []; private isListening = false; private recognizer: any; @@ -17,8 +29,16 @@ export default class DictationManager { this.recognizer.continuous = true; } + @observable public current = ""; + + @action finish = (handler: any, data: any) => { + this.current = data; handler(data); + this.stop(); + } + + stop = () => { this.isListening = false; this.recognizer.stop(); } @@ -36,4 +56,72 @@ export default class DictationManager { } + private sanitize = (title: string) => { + return title.replace("...", "").toLowerCase().trim(); + } + + public registerStatic = (keys: Array, action: Action, overwrite = false) => { + let success = true; + keys.forEach(key => { + key = this.sanitize(key); + let existing = this.registeredCommands.get(key); + if (!existing || overwrite) { + this.registeredCommands.set(key, action); + } else { + success = false; + } + }); + return success; + } + + public interpretNumber = (number: string) => { + let initial = parseInt(number); + if (!isNaN(initial)) { + return initial; + } + let converted = converter.wordsToNumbers(number, { fuzzy: true }); + if (converted === null) { + return NaN; + } + return typeof converted === "string" ? parseInt(converted) : converted; + } + + public registerDynamic = (dynamicKey: RegExp, action: DynamicAction) => { + this.registeredRegexes.push({ + key: dynamicKey, + value: action + }); + } + + public execute = async (phrase: string) => { + let target = SelectionManager.SelectedDocuments()[0]; + if (!target) { + return; + } + let batch = UndoManager.StartBatch("Dictation Action"); + phrase = this.sanitize(phrase); + + let registeredAction = this.registeredCommands.get(phrase); + if (registeredAction) { + await registeredAction(target); + return true; + } + + let success = false; + for (let entry of this.registeredRegexes) { + let regex = entry.key; + let registeredDynamicAction = entry.value; + let matches = regex.exec(phrase); + regex.lastIndex = 0; + if (matches !== null) { + await registeredDynamicAction(target, matches); + success = true; + break; + } + } + batch.end(); + + return success; + } + } \ No newline at end of file diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 373584b4e..1d3c77ec7 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -73,6 +73,7 @@ export default class KeyManager { } MainView.Instance.toggleColorPicker(true); SelectionManager.DeselectAll(); + DictationManager.Instance.stop(); break; case "delete": case "backspace": @@ -106,10 +107,11 @@ export default class KeyManager { switch (keyname) { case " ": - let transcript = await DictationManager.Instance.listen(); + console.log("Listening..."); + let analyzer = DictationManager.Instance; + let transcript = await analyzer.listen(); console.log(`I heard${transcript ? `: ${transcript.toLowerCase()}` : " nothing: I thought I was still listening from an earlier session."}`); - let command: ContextMenuProps | undefined; - transcript && (command = ContextMenu.Instance.findByDescription(transcript, true)) && "event" in command && command.event(); + transcript && analyzer.execute(transcript); } return { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 91c8fe57c..36ac96907 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -39,6 +39,7 @@ import { FilterBox } from './search/FilterBox'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { ClientUtils } from '../util/ClientUtils'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; +import DictationManager from '../util/DictationManager'; @observer export class MainView extends React.Component { @@ -459,6 +460,7 @@ export class MainView extends React.Component { render() { return (
+

{DictationManager.Instance.current}

{this.mainContent} diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index c595a4c56..3f88ed98c 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -22,6 +22,24 @@ export enum CollectionViewType { Masonry } +export namespace CollectionViewType { + + const stringMapping = new Map([ + ["invalid", CollectionViewType.Invalid], + ["freeform", CollectionViewType.Freeform], + ["schema", CollectionViewType.Schema], + ["docking", CollectionViewType.Docking], + ["tree", CollectionViewType.Tree], + ["stacking", CollectionViewType.Stacking], + ["masonry", CollectionViewType.Masonry] + ]); + + export const ValueOf = (value: string) => { + return stringMapping.get(value.toLowerCase()); + }; + +} + export interface CollectionRenderProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; removeDocument: (document: Doc) => boolean; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8dac785e1..7856f3718 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -20,7 +20,7 @@ import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; -import { CollectionSubView } from "../CollectionSubView"; +import { CollectionSubView, SubCollectionViewProps } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; @@ -38,6 +38,7 @@ import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsA import { undo } from "prosemirror-history"; import { number } from "prop-types"; import { ContextMenu } from "../../ContextMenu"; +import DictationManager from "../../../util/DictationManager"; library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass); @@ -121,11 +122,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); } + constructor(props: SubCollectionViewProps) { + super(props); + let fixed = DictationManager.Instance.registerStatic; + fixed(["Unset Fit To Container", "Set Fit To Container"], this.fitToContainer); + fixed(["Arrange contents in grid"], this.arrangeContents); + fixed(["Analyze Strokes"], this.analyzeStrokes); + } + @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } - @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { @@ -516,50 +524,62 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } + fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox; + + arrangeContents = async () => { + const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); + UndoManager.RunInBatch(() => { + if (docs) { + let startX = this.Document.panX || 0; + let x = startX; + let y = this.Document.panY || 0; + let i = 0; + const width = Math.max(...docs.map(doc => NumCast(doc.width))); + const height = Math.max(...docs.map(doc => NumCast(doc.height))); + for (const doc of docs) { + doc.x = x; + doc.y = y; + x += width + 20; + if (++i === 6) { + i = 0; + x = startX; + y += height + 20; + } + } + } + }, "arrange contents"); + } + + analyzeStrokes = async () => { + let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); + if (!data) { + return; + } + let relevantKeys = ["inkAnalysis", "handwriting"]; + CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData); + } + onContextMenu = () => { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, - event: async () => this.props.Document.fitToBox = !this.fitToBox, + event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); layoutItems.push({ description: "Arrange contents in grid", - icon: "table", - event: async () => { - const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); - UndoManager.RunInBatch(() => { - if (docs) { - let startX = this.Document.panX || 0; - let x = startX; - let y = this.Document.panY || 0; - let i = 0; - const width = Math.max(...docs.map(doc => NumCast(doc.width))); - const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs) { - doc.x = x; - doc.y = y; - x += width + 20; - if (++i === 6) { - i = 0; - x = startX; - y += height + 20; - } - } - } - }, "arrange contents"); - } + event: this.arrangeContents, + icon: "table" }); - ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); ContextMenu.Instance.addItem({ - description: "Analyze Strokes", event: async () => { - let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); - if (!data) { - return; - } - let relevantKeys = ["inkAnalysis", "handwriting"]; - CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData); - }, icon: "paint-brush" + description: "Layout...", + subitems: layoutItems, + icon: "compass" + }); + ContextMenu.Instance.addItem({ + description: "Analyze Strokes", + event: this.analyzeStrokes, + icon: "paint-brush" }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dc56c1c8f..00416ca42 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -43,6 +43,7 @@ import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-i import { DocumentDecorations } from '../DocumentDecorations'; import { CognitiveServices } from '../../cognitive_services/CognitiveServices'; import DictationManager from '../../util/DictationManager'; +import { CollectionViewType } from '../collections/CollectionBaseView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -153,6 +154,43 @@ export class DocumentView extends DocComponent(Docu constructor(props: DocumentViewProps) { super(props); + let fixed = DictationManager.Instance.registerStatic; + let dynamic = DictationManager.Instance.registerDynamic; + fixed(["Open Fields"], DocumentView.OpenFieldsDictation); + fixed(["Clear"], DocumentView.ClearChildren); + dynamic(/create (\w+) documents of type (image|nested collection)/g, DocumentView.BuildLayout); + dynamic(/view as (freeform|stacking|masonry|schema|tree)/g, DocumentView.SetViewMode); + } + + public static ClearChildren = (target: DocumentView) => { + Doc.GetProto(target.props.Document).data = new List(); + } + + public static BuildLayout = (target: DocumentView, matches: RegExpExecArray) => { + let count = DictationManager.Instance.interpretNumber(matches[1]); + let what = matches[2]; + if (!("viewType" in target.props.Document)) { + return; + } + let dataDoc = Doc.GetProto(target.props.Document); + let fieldKey = "data"; + for (let i = 0; i < count; i++) { + let created: Doc | undefined; + switch (what) { + case "image": + created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + break; + case "nested collection": + created = Docs.Create.FreeformDocument([], {}); + break; + } + created && Doc.AddDocToList(dataDoc, fieldKey, created); + } + } + + public static SetViewMode = (target: DocumentView, matches: RegExpExecArray) => { + let mode = CollectionViewType.ValueOf(matches[1]); + mode && (target.props.Document.viewType = mode); } _animateToIconDisposer?: IReactionDisposer; @@ -413,7 +451,15 @@ export class DocumentView extends DocComponent(Docu deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } @undoBatch - fieldsClicked = (): void => { let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.dataDoc, "onRight"); } + fieldsClicked = (): void => { + let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); + this.props.addDocTab(kvp, this.dataDoc, "onRight"); + } + + public static OpenFieldsDictation = (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); + } @undoBatch makeBtnClicked = (): void => { -- cgit v1.2.3-70-g09d2 From 31a8bb32e696d58816329b66cfc5b92907494d1b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 30 Jul 2019 23:29:23 -0400 Subject: refactor --- src/client/util/DictationManager.ts | 88 +++++++++++++++++++++++++++------ src/client/views/nodes/DocumentView.tsx | 46 ----------------- 2 files changed, 72 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 60b25afc5..6d67f6d6d 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -4,6 +4,10 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; import { UndoManager } from "./UndoManager"; import * as converter from "words-to-numbers"; +import { Doc } from "../../new_fields/Doc"; +import { List } from "../../new_fields/List"; +import { Docs } from "../documents/Documents"; +import { CollectionViewType } from "../views/collections/CollectionBaseView"; namespace CORE { export interface IWindow extends Window { @@ -12,14 +16,12 @@ namespace CORE { } const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; -export type Action = (target: DocumentView) => any | Promise; -export type DynamicAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegexEntry = { key: RegExp, value: DynamicAction }; +export type IndependentAction = (target: DocumentView) => any | Promise; +export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; +export type RegexEntry = { key: RegExp, value: DependentAction }; export default class DictationManager { public static Instance = new DictationManager(); - private registeredCommands = new Map(); - private registeredRegexes: RegexEntry[] = []; private isListening = false; private recognizer: any; @@ -60,13 +62,13 @@ export default class DictationManager { return title.replace("...", "").toLowerCase().trim(); } - public registerStatic = (keys: Array, action: Action, overwrite = false) => { + public registerStatic = (keys: Array, action: IndependentAction, overwrite = false) => { let success = true; keys.forEach(key => { key = this.sanitize(key); - let existing = this.registeredCommands.get(key); + let existing = RegisteredCommands.Independent.get(key); if (!existing || overwrite) { - this.registeredCommands.set(key, action); + RegisteredCommands.Independent.set(key, action); } else { success = false; } @@ -86,8 +88,8 @@ export default class DictationManager { return typeof converted === "string" ? parseInt(converted) : converted; } - public registerDynamic = (dynamicKey: RegExp, action: DynamicAction) => { - this.registeredRegexes.push({ + public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => { + RegisteredCommands.Dependent.push({ key: dynamicKey, value: action }); @@ -101,20 +103,20 @@ export default class DictationManager { let batch = UndoManager.StartBatch("Dictation Action"); phrase = this.sanitize(phrase); - let registeredAction = this.registeredCommands.get(phrase); - if (registeredAction) { - await registeredAction(target); + let independentAction = RegisteredCommands.Independent.get(phrase); + if (independentAction) { + await independentAction(target); return true; } let success = false; - for (let entry of this.registeredRegexes) { + for (let entry of RegisteredCommands.Dependent) { let regex = entry.key; - let registeredDynamicAction = entry.value; + let dependentAction = entry.value; let matches = regex.exec(phrase); regex.lastIndex = 0; if (matches !== null) { - await registeredDynamicAction(target, matches); + await dependentAction(target, matches); success = true; break; } @@ -124,4 +126,58 @@ export default class DictationManager { return success; } +} + +export namespace RegisteredCommands { + + export const Independent = new Map([ + + ["clear", (target: DocumentView) => { + Doc.GetProto(target.props.Document).data = new List(); + }], + + ["open fields", (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); + }] + + ]); + + export const Dependent = new Array( + + { + key: /create (\w+) documents of type (image|nested collection)/g, + value: (target: DocumentView, matches: RegExpExecArray) => { + let count = DictationManager.Instance.interpretNumber(matches[1]); + let what = matches[2]; + if (!("viewType" in target.props.Document)) { + return; + } + let dataDoc = Doc.GetProto(target.props.Document); + let fieldKey = "data"; + for (let i = 0; i < count; i++) { + let created: Doc | undefined; + switch (what) { + case "image": + created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + break; + case "nested collection": + created = Docs.Create.FreeformDocument([], {}); + break; + } + created && Doc.AddDocToList(dataDoc, fieldKey, created); + } + } + }, + + { + key: /view as (freeform|stacking|masonry|schema|tree)/g, + value: (target: DocumentView, matches: RegExpExecArray) => { + let mode = CollectionViewType.ValueOf(matches[1]); + mode && (target.props.Document.viewType = mode); + } + } + + ); + } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 00416ca42..db8203167 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -152,47 +152,6 @@ export class DocumentView extends DocComponent(Docu set templates(templates: List) { this.props.Document.templates = templates; } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); - constructor(props: DocumentViewProps) { - super(props); - let fixed = DictationManager.Instance.registerStatic; - let dynamic = DictationManager.Instance.registerDynamic; - fixed(["Open Fields"], DocumentView.OpenFieldsDictation); - fixed(["Clear"], DocumentView.ClearChildren); - dynamic(/create (\w+) documents of type (image|nested collection)/g, DocumentView.BuildLayout); - dynamic(/view as (freeform|stacking|masonry|schema|tree)/g, DocumentView.SetViewMode); - } - - public static ClearChildren = (target: DocumentView) => { - Doc.GetProto(target.props.Document).data = new List(); - } - - public static BuildLayout = (target: DocumentView, matches: RegExpExecArray) => { - let count = DictationManager.Instance.interpretNumber(matches[1]); - let what = matches[2]; - if (!("viewType" in target.props.Document)) { - return; - } - let dataDoc = Doc.GetProto(target.props.Document); - let fieldKey = "data"; - for (let i = 0; i < count; i++) { - let created: Doc | undefined; - switch (what) { - case "image": - created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); - break; - case "nested collection": - created = Docs.Create.FreeformDocument([], {}); - break; - } - created && Doc.AddDocToList(dataDoc, fieldKey, created); - } - } - - public static SetViewMode = (target: DocumentView, matches: RegExpExecArray) => { - let mode = CollectionViewType.ValueOf(matches[1]); - mode && (target.props.Document.viewType = mode); - } - _animateToIconDisposer?: IReactionDisposer; _reactionDisposer?: IReactionDisposer; @action @@ -456,11 +415,6 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(kvp, this.dataDoc, "onRight"); } - public static OpenFieldsDictation = (target: DocumentView) => { - let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); - } - @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); -- cgit v1.2.3-70-g09d2 From 8a87f7110b56ca96b3960f6fb3917c7ed8c7a814 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 03:21:59 -0400 Subject: overlay completed --- src/client/util/DictationManager.ts | 12 +++---- src/client/views/GlobalKeyHandler.ts | 24 +++++++++---- src/client/views/Main.scss | 68 +++++++++++++++++++++++++++--------- src/client/views/MainView.tsx | 55 +++++++++++++++++++++++++++-- 4 files changed, 127 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 6d67f6d6d..80efe12cd 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,5 +1,5 @@ import { string } from "prop-types"; -import { observable, action } from "mobx"; +import { observable, action, autorun } from "mobx"; import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; import { UndoManager } from "./UndoManager"; @@ -8,6 +8,7 @@ import { Doc } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { MainView } from "../views/MainView"; namespace CORE { export interface IWindow extends Window { @@ -22,8 +23,8 @@ export type RegexEntry = { key: RegExp, value: DependentAction }; export default class DictationManager { public static Instance = new DictationManager(); - private isListening = false; private recognizer: any; + private isListening = false; constructor() { this.recognizer = new webkitSpeechRecognition(); @@ -31,18 +32,14 @@ export default class DictationManager { this.recognizer.continuous = true; } - @observable public current = ""; - - @action finish = (handler: any, data: any) => { - this.current = data; handler(data); this.stop(); } stop = () => { - this.isListening = false; this.recognizer.stop(); + this.isListening = false; } listen = () => { @@ -55,7 +52,6 @@ export default class DictationManager { this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript); this.recognizer.onerror = (e: any) => this.finish(reject, e); }); - } private sanitize = (title: string) => { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 1d3c77ec7..a6020bd3f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -3,7 +3,7 @@ import { SelectionManager } from "../util/SelectionManager"; import { CollectionDockingView } from "./collections/CollectionDockingView"; import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; -import { action } from "mobx"; +import { action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import DictationManager from "../util/DictationManager"; @@ -74,6 +74,7 @@ export default class KeyManager { MainView.Instance.toggleColorPicker(true); SelectionManager.DeselectAll(); DictationManager.Instance.stop(); + MainView.Instance.dictationOverlayVisible = false; break; case "delete": case "backspace": @@ -107,11 +108,22 @@ export default class KeyManager { switch (keyname) { case " ": - console.log("Listening..."); - let analyzer = DictationManager.Instance; - let transcript = await analyzer.listen(); - console.log(`I heard${transcript ? `: ${transcript.toLowerCase()}` : " nothing: I thought I was still listening from an earlier session."}`); - transcript && analyzer.execute(transcript); + let main = MainView.Instance; + main.dictationOverlayVisible = true; + main.isListening = true; + let manager = DictationManager.Instance; + let command = await manager.listen(); + main.isListening = false; + if (!command) { + break; + } + command = command.toLowerCase(); + main.dictatedPhrase = command; + main.dictationSuccess = await manager.execute(command); + setTimeout(() => { + main.dictationOverlayVisible = false; + main.dictationSuccess = undefined; + }, 3000); } return { diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index a16123476..8e57b88c3 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -24,7 +24,7 @@ div { .jsx-parser { width: 100%; - height:100%; + height: 100%; pointer-events: none; border-radius: inherit; } @@ -119,6 +119,7 @@ button:hover { margin-bottom: 10px; } } + .toolbar-color-picker { background-color: $light-color; border-radius: 5px; @@ -128,6 +129,7 @@ button:hover { left: -3px; box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; } + .toolbar-color-button { border-radius: 11px; width: 22px; @@ -146,7 +148,7 @@ button:hover { bottom: 22px; left: 250px; - > label { + >label { background: $dark-color; color: $light-color; display: inline-block; @@ -168,15 +170,15 @@ button:hover { transform: scale(1.15); } - > input { + >input { display: none; } - > input:not(:checked)~#add-options-content { + >input:not(:checked)~#add-options-content { display: none; } - > input:checked~label { + >input:checked~label { transform: rotate(45deg); transition: transform 0.5s; cursor: pointer; @@ -221,7 +223,7 @@ ul#add-options-list { list-style: none; padding: 5 0 0 0; - > li { + >li { display: inline-block; padding: 0; } @@ -231,7 +233,7 @@ ul#add-options-list { height: 100%; position: absolute; display: flex; - flex-direction:column; + flex-direction: column; } .mainView-libraryHandle { @@ -243,21 +245,55 @@ ul#add-options-list { position: absolute; z-index: 1; } + .svg-inline--fa { vertical-align: unset; } + .mainView-workspace { - height:200px; - position:relative; - display:flex; + height: 200px; + position: relative; + display: flex; } + .mainView-library { - height:75%; - position:relative; - display:flex; + height: 75%; + position: relative; + display: flex; } + .mainView-recentlyClosed { - height:25%; - position:relative; - display:flex; + height: 25%; + position: relative; + display: flex; +} + +.dictation-prompt { + position: absolute; + z-index: 1000; + text-align: center; + justify-content: center; + align-self: center; + align-content: center; + padding: 20px; + background: gainsboro; + border-radius: 10px; + border: 3px solid black; + box-shadow: #00000044 5px 5px 10px; + transform: translate(-50%, -50%); + top: 50%; + font-style: italic; + left: 50%; + transition: 0.5s all ease; + pointer-events: none; +} + +.dictation-prompt-overlay { + width: 100%; + height: 100%; + position: absolute; + background: darkslategray; + z-index: 999; + transition: 0.5s all ease; + pointer-events: none; } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 36ac96907..d6449cffc 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -39,7 +39,6 @@ import { FilterBox } from './search/FilterBox'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { ClientUtils } from '../util/ClientUtils'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; -import DictationManager from '../util/DictationManager'; @observer export class MainView extends React.Component { @@ -48,6 +47,12 @@ export class MainView extends React.Component { @observable private _workspacesShown: boolean = false; @observable public pwidth: number = 0; @observable public pheight: number = 0; + + @observable private dictationState = "Listening..."; + @observable private dictationSuccessState: boolean | undefined = undefined; + @observable private dictationDisplayState = false; + @observable private dictationListeningState = false; + @computed private get mainContainer(): Opt { return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc)); } @@ -65,6 +70,38 @@ export class MainView extends React.Component { } } + public get dictatedPhrase() { + return this.dictationState; + } + + public set dictatedPhrase(value: string) { + runInAction(() => this.dictationState = value); + } + + public get dictationSuccess() { + return this.dictationSuccessState; + } + + public set dictationSuccess(value: boolean | undefined) { + runInAction(() => this.dictationSuccessState = value); + } + + public get dictationOverlayVisible() { + return this.dictationDisplayState; + } + + public set dictationOverlayVisible(value: boolean) { + runInAction(() => this.dictationDisplayState = value); + } + + public get isListening() { + return this.dictationListeningState; + } + + public set isListening(value: boolean) { + runInAction(() => this.dictationListeningState = value); + } + componentWillMount() { var tag = document.createElement('script'); @@ -458,9 +495,23 @@ export class MainView extends React.Component { } render() { + let display = this.dictationOverlayVisible; + let success = this.dictationSuccess; + let result = this.isListening ? "Listening..." : `"${this.dictatedPhrase}"`; return (
-

{DictationManager.Instance.current}

+
{result}
+
{this.mainContent} -- cgit v1.2.3-70-g09d2 From b2c52f3978d00f759489b38544844ed3206db611 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 03:47:25 -0400 Subject: undo batching error fixed --- src/client/util/DictationManager.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 80efe12cd..f0fd7cbde 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -102,6 +102,7 @@ export default class DictationManager { let independentAction = RegisteredCommands.Independent.get(phrase); if (independentAction) { await independentAction(target); + batch.end(); return true; } -- cgit v1.2.3-70-g09d2 From 49faa4e76f91fed04bb1923d81dd23d57a157a63 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 31 Jul 2019 07:35:41 -0400 Subject: grouping experiment --- .../collectionFreeForm/CollectionFreeFormView.tsx | 49 +++++++++++++++++++++- src/client/views/nodes/DocumentView.tsx | 1 + 2 files changed, 49 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8dac785e1..c4c3ba084 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -38,6 +38,7 @@ import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsA import { undo } from "prosemirror-history"; import { number } from "prop-types"; import { ContextMenu } from "../../ContextMenu"; +import { SwatchesPicker } from "react-color"; library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass); @@ -125,7 +126,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } - + intersectRect(r1: { left: number, top: number, width: number, height: number }, + r2: { left: number, top: number, width: number, height: number }) { + return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); + } + bounsdSelect(doc: Doc, doc2: Doc) { + var x2 = NumCast(doc2.x) - 25; + var y2 = NumCast(doc2.y) - 25; + var w2 = NumCast(doc2.width) + 25; + var h2 = NumCast(doc2.height) + 25; + var x = NumCast(doc.x) - 25; + var y = NumCast(doc.y) - 25; + var w = NumCast(doc.width) + 25; + var h = NumCast(doc.height) + 25; + if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) { + return true; + } + return false; + } @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { @@ -151,6 +169,35 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.bringToFront(d); }); + + let sets: (Doc[])[] = [] + this.childDocs.map(c => { + let included = [] + for (let i = 0; i < sets.length; i++) { + for (let j = 0; j < sets[i].length; j++) { + if (this.bounsdSelect(c, sets[i][j])) { + included.push(i); + break; + } + } + } + if (included.length === 0) + sets.push([c]); + else if (included.length === 1) + sets[included[0]].push(c); + else { + sets[included[0]].push(c); + for (let s = 1; s < included.length; s++) { + sets[included[0]].push(...sets[included[s]]); + sets[included[s]].length = 0; + } + } + }); + for (let s = 0; s < sets.length; s++) { + for (let i = 0; i < sets[s].length; i++) { + Doc.GetProto(sets[s][i]).cluster = s; + } + } } } else if (de.data instanceof DragManager.AnnotationDragData) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4b5cf3a43..2dc2c18a3 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -694,6 +694,7 @@ export class DocumentView extends DocComponent(Docu color: foregroundColor, outlineColor: "maroon", outlineStyle: "dashed", + boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`, outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? -- cgit v1.2.3-70-g09d2 From df444357c153abf8daf87d9f80f4fe2bcfba00b9 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 13:40:47 -0400 Subject: filtering --- src/client/util/DictationManager.ts | 51 ++++++++++++++++++++++++++++--------- src/new_fields/Types.ts | 8 +++--- 2 files changed, 44 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index f0fd7cbde..9c539e86a 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -4,11 +4,14 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; import { UndoManager } from "./UndoManager"; import * as converter from "words-to-numbers"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, Field } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; import { MainView } from "../views/MainView"; +import { listSpec } from "../../new_fields/Schema"; +import { Cast, ToConstructor, ListSpec, CastCtor } from "../../new_fields/Types"; +import { ImageField } from "../../new_fields/URLField"; namespace CORE { export interface IWindow extends Window { @@ -20,6 +23,7 @@ const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; export type RegexEntry = { key: RegExp, value: DependentAction }; +export type RegistrationUnit = { filter: Predicate, action: T }; export default class DictationManager { public static Instance = new DictationManager(); @@ -58,13 +62,17 @@ export default class DictationManager { return title.replace("...", "").toLowerCase().trim(); } - public registerStatic = (keys: Array, action: IndependentAction, overwrite = false) => { + public registerStatic = (keys: Array, action: IndependentAction, filter: Predicate, overwrite = false) => { let success = true; keys.forEach(key => { key = this.sanitize(key); let existing = RegisteredCommands.Independent.get(key); if (!existing || overwrite) { - RegisteredCommands.Independent.set(key, action); + let unit = { + filter: filter, + action: action + }; + RegisteredCommands.Independent.set(key, unit); } else { success = false; } @@ -99,9 +107,9 @@ export default class DictationManager { let batch = UndoManager.StartBatch("Dictation Action"); phrase = this.sanitize(phrase); - let independentAction = RegisteredCommands.Independent.get(phrase); - if (independentAction) { - await independentAction(target); + let unit = RegisteredCommands.Independent.get(phrase); + if (unit && unit.filter(target)) { + await unit.action(target); batch.end(); return true; } @@ -127,15 +135,21 @@ export default class DictationManager { export namespace RegisteredCommands { - export const Independent = new Map([ + export const Independent = new Map>([ - ["clear", (target: DocumentView) => { - Doc.GetProto(target.props.Document).data = new List(); + ["clear", { + action: (target: DocumentView) => { + Doc.GetProto(target.props.Document).data = new List(); + }, + filter: Filters.isCollectionView }], - ["open fields", (target: DocumentView) => { - let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); + ["open fields", { + action: (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); + }, + filter: Filters.isImageView }] ]); @@ -177,4 +191,17 @@ export namespace RegisteredCommands { ); +} + +export type Predicate = (target: DocumentView) => boolean; + +export namespace Filters { + + const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor); + + export const isCollectionView: Predicate = (target: DocumentView) => tryCast(target, listSpec(Doc)) !== undefined; + + export const isImageView: Predicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; + + } \ No newline at end of file diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts index 565ae2ee3..09cbff25e 100644 --- a/src/new_fields/Types.ts +++ b/src/new_fields/Types.ts @@ -48,9 +48,11 @@ export interface Interface { } export type WithoutRefField = T extends RefField ? never : T; -export function Cast | ListSpec>(field: FieldResult, ctor: T): FieldResult>; -export function Cast | ListSpec>(field: FieldResult, ctor: T, defaultVal: WithoutList>> | null): WithoutList>; -export function Cast | ListSpec>(field: FieldResult, ctor: T, defaultVal?: ToType | null): FieldResult> | undefined { +export type CastCtor = ToConstructor | ListSpec; + +export function Cast(field: FieldResult, ctor: T): FieldResult>; +export function Cast(field: FieldResult, ctor: T, defaultVal: WithoutList>> | null): WithoutList>; +export function Cast(field: FieldResult, ctor: T, defaultVal?: ToType | null): FieldResult> | undefined { if (field instanceof Promise) { return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal; } -- cgit v1.2.3-70-g09d2 From 2b8c2ddcfefebdce9fdcc3e65476f6abc8b7e832 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 14:49:15 -0400 Subject: none-filter --- src/client/util/DictationManager.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 9c539e86a..e14000555 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -23,7 +23,7 @@ const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; export type RegexEntry = { key: RegExp, value: DependentAction }; -export type RegistrationUnit = { filter: Predicate, action: T }; +export type RegistrationUnit = { action: T, filter?: Predicate }; export default class DictationManager { public static Instance = new DictationManager(); @@ -104,31 +104,32 @@ export default class DictationManager { if (!target) { return; } - let batch = UndoManager.StartBatch("Dictation Action"); phrase = this.sanitize(phrase); let unit = RegisteredCommands.Independent.get(phrase); - if (unit && unit.filter(target)) { - await unit.action(target); - batch.end(); - return true; + if (unit) { + if (!unit.filter || unit.filter(target)) { + let batch = UndoManager.StartBatch("Dictation Independent Action"); + await unit.action(target); + batch.end(); + return true; + } } - let success = false; for (let entry of RegisteredCommands.Dependent) { let regex = entry.key; let dependentAction = entry.value; let matches = regex.exec(phrase); regex.lastIndex = 0; if (matches !== null) { + let batch = UndoManager.StartBatch("Dictation Dependent Action"); await dependentAction(target, matches); - success = true; - break; + batch.end(); + return true; } } - batch.end(); - return success; + return false; } } @@ -203,5 +204,4 @@ export namespace Filters { export const isImageView: Predicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; - } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 0eaaf47ec89a32aafb25add36a3d36bb5a2a1370 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 14:59:51 -0400 Subject: clean up --- src/client/util/DictationManager.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index e14000555..88b79be8a 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -2,7 +2,7 @@ import { string } from "prop-types"; import { observable, action, autorun } from "mobx"; import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; -import { UndoManager } from "./UndoManager"; +import { UndoManager, undoBatch } from "./UndoManager"; import * as converter from "words-to-numbers"; import { Doc, Field } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; @@ -23,7 +23,7 @@ const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; export type RegexEntry = { key: RegExp, value: DependentAction }; -export type RegistrationUnit = { action: T, filter?: Predicate }; +export type RegistrationUnit = { action: T, validate?: Predicate }; export default class DictationManager { public static Instance = new DictationManager(); @@ -99,6 +99,7 @@ export default class DictationManager { }); } + @undoBatch public execute = async (phrase: string) => { let target = SelectionManager.SelectedDocuments()[0]; if (!target) { @@ -108,10 +109,8 @@ export default class DictationManager { let unit = RegisteredCommands.Independent.get(phrase); if (unit) { - if (!unit.filter || unit.filter(target)) { - let batch = UndoManager.StartBatch("Dictation Independent Action"); + if (!unit.validate || unit.validate(target)) { await unit.action(target); - batch.end(); return true; } } @@ -122,9 +121,7 @@ export default class DictationManager { let matches = regex.exec(phrase); regex.lastIndex = 0; if (matches !== null) { - let batch = UndoManager.StartBatch("Dictation Dependent Action"); await dependentAction(target, matches); - batch.end(); return true; } } @@ -142,7 +139,7 @@ export namespace RegisteredCommands { action: (target: DocumentView) => { Doc.GetProto(target.props.Document).data = new List(); }, - filter: Filters.isCollectionView + validate: Filters.isCollectionView }], ["open fields", { @@ -150,7 +147,7 @@ export namespace RegisteredCommands { let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); target.props.addDocTab(kvp, target.dataDoc, "onRight"); }, - filter: Filters.isImageView + validate: Filters.isImageView }] ]); -- cgit v1.2.3-70-g09d2 From 7611bc1ec475dd398e172ba5dbdb9c48b26663c2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 18:11:03 -0400 Subject: cleanup and validation of dependent commands --- src/client/util/DictationManager.ts | 59 +++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 88b79be8a..80fa63226 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -22,8 +22,8 @@ namespace CORE { const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegexEntry = { key: RegExp, value: DependentAction }; -export type RegistrationUnit = { action: T, validate?: Predicate }; +export type RegistrationEntry = { action: IndependentAction, validate?: ActionPredicate }; +export type RegexEntry = { expression: RegExp, action: DependentAction, validate?: ActionPredicate }; export default class DictationManager { public static Instance = new DictationManager(); @@ -62,7 +62,7 @@ export default class DictationManager { return title.replace("...", "").toLowerCase().trim(); } - public registerStatic = (keys: Array, action: IndependentAction, filter: Predicate, overwrite = false) => { + public registerStatic = (keys: Array, action: IndependentAction, filter: ActionPredicate, overwrite = false) => { let success = true; keys.forEach(key => { key = this.sanitize(key); @@ -94,8 +94,8 @@ export default class DictationManager { public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => { RegisteredCommands.Dependent.push({ - key: dynamicKey, - value: action + expression: dynamicKey, + action: action }); } @@ -107,21 +107,18 @@ export default class DictationManager { } phrase = this.sanitize(phrase); - let unit = RegisteredCommands.Independent.get(phrase); - if (unit) { - if (!unit.validate || unit.validate(target)) { - await unit.action(target); - return true; - } + let entry = RegisteredCommands.Independent.get(phrase); + if (entry && (!entry.validate || entry.validate(target))) { + await entry.action(target); + return true; } for (let entry of RegisteredCommands.Dependent) { - let regex = entry.key; - let dependentAction = entry.value; + let regex = entry.expression; let matches = regex.exec(phrase); regex.lastIndex = 0; - if (matches !== null) { - await dependentAction(target, matches); + if (matches !== null && (!entry.validate || entry.validate(target))) { + await entry.action(target, matches); return true; } } @@ -133,21 +130,20 @@ export default class DictationManager { export namespace RegisteredCommands { - export const Independent = new Map>([ + export const Independent = new Map([ ["clear", { action: (target: DocumentView) => { Doc.GetProto(target.props.Document).data = new List(); }, - validate: Filters.isCollectionView + validate: Validators.isCollectionView }], ["open fields", { action: (target: DocumentView) => { let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); target.props.addDocTab(kvp, target.dataDoc, "onRight"); - }, - validate: Filters.isImageView + } }] ]); @@ -155,13 +151,10 @@ export namespace RegisteredCommands { export const Dependent = new Array( { - key: /create (\w+) documents of type (image|nested collection)/g, - value: (target: DocumentView, matches: RegExpExecArray) => { + expression: /create (\w+) documents of type (image|nested collection)/g, + action: (target: DocumentView, matches: RegExpExecArray) => { let count = DictationManager.Instance.interpretNumber(matches[1]); let what = matches[2]; - if (!("viewType" in target.props.Document)) { - return; - } let dataDoc = Doc.GetProto(target.props.Document); let fieldKey = "data"; for (let i = 0; i < count; i++) { @@ -176,29 +169,31 @@ export namespace RegisteredCommands { } created && Doc.AddDocToList(dataDoc, fieldKey, created); } - } + }, + validate: Validators.isCollectionView }, { - key: /view as (freeform|stacking|masonry|schema|tree)/g, - value: (target: DocumentView, matches: RegExpExecArray) => { + expression: /view as (freeform|stacking|masonry|schema|tree)/g, + action: (target: DocumentView, matches: RegExpExecArray) => { let mode = CollectionViewType.ValueOf(matches[1]); mode && (target.props.Document.viewType = mode); - } + }, + validate: Validators.isCollectionView } ); } -export type Predicate = (target: DocumentView) => boolean; +export type ActionPredicate = (target: DocumentView) => boolean; -export namespace Filters { +export namespace Validators { const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor); - export const isCollectionView: Predicate = (target: DocumentView) => tryCast(target, listSpec(Doc)) !== undefined; + export const isCollectionView: ActionPredicate = (target: DocumentView) => tryCast(target, listSpec(Doc)) !== undefined; - export const isImageView: Predicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; + export const isImageView: ActionPredicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From bbad2e964edb8ae58a5b611003dcba5ba170503d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 18:21:52 -0400 Subject: compiles --- src/client/util/DictationManager.ts | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 80fa63226..5c318fc9b 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -19,10 +19,21 @@ namespace CORE { } } +export namespace Validators { + + const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor); + + export const isCollectionView: ActionPredicate = (target: DocumentView) => tryCast(target, listSpec(Doc)) !== undefined; + + export const isImageView: ActionPredicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; + +} + const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; export type RegistrationEntry = { action: IndependentAction, validate?: ActionPredicate }; +export type ActionPredicate = (target: DocumentView) => boolean; export type RegexEntry = { expression: RegExp, action: DependentAction, validate?: ActionPredicate }; export default class DictationManager { @@ -62,15 +73,15 @@ export default class DictationManager { return title.replace("...", "").toLowerCase().trim(); } - public registerStatic = (keys: Array, action: IndependentAction, filter: ActionPredicate, overwrite = false) => { + public registerStatic = (keys: Array, action: IndependentAction, filter?: ActionPredicate) => { let success = true; keys.forEach(key => { key = this.sanitize(key); let existing = RegisteredCommands.Independent.get(key); - if (!existing || overwrite) { + if (!existing) { let unit = { - filter: filter, - action: action + action: action, + filter: filter }; RegisteredCommands.Independent.set(key, unit); } else { @@ -184,16 +195,4 @@ export namespace RegisteredCommands { ); -} - -export type ActionPredicate = (target: DocumentView) => boolean; - -export namespace Validators { - - const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor); - - export const isCollectionView: ActionPredicate = (target: DocumentView) => tryCast(target, listSpec(Doc)) !== undefined; - - export const isImageView: ActionPredicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; - } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 3b628238e9cf155941d949086de44320ceb8e18d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 18:23:45 -0400 Subject: try cast fix --- src/client/util/DictationManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 5c318fc9b..3e130f984 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -21,11 +21,11 @@ namespace CORE { export namespace Validators { - const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor); + const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; - export const isCollectionView: ActionPredicate = (target: DocumentView) => tryCast(target, listSpec(Doc)) !== undefined; + export const isCollectionView: ActionPredicate = (target: DocumentView) => tryCast(target, listSpec(Doc)); - export const isImageView: ActionPredicate = (target: DocumentView) => tryCast(target, ImageField) !== undefined; + export const isImageView: ActionPredicate = (target: DocumentView) => tryCast(target, ImageField); } -- cgit v1.2.3-70-g09d2 From d1debe4de37edfcafdd3446c4918488521af3a84 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 31 Jul 2019 19:26:22 -0400 Subject: fixed treeview omitting documents --- .../views/collections/CollectionTreeView.tsx | 46 +++++++++------------- 1 file changed, 19 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b1e6eada0..fa75c680a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -70,30 +70,16 @@ class TreeView extends React.Component { private _header?: React.RefObject = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); - @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "data"); } + @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.fieldKey); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } @observable _collapsed: boolean = true; @computed get fieldKey() { - let target = this.props.document; - let keys = Array.from(Object.keys(target)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set - if (target.proto instanceof Doc) { - let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set - keys.push(...arr); - while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1); - } - let keyList: string[] = []; - keys.map(key => { - let docList = Cast(this.dataDoc[key], listSpec(Doc)); - if (docList && docList.length > 0) { - keyList.push(key); - } - }); let layout = StrCast(this.props.document.layout); - if (layout.indexOf("fieldKey={\"") !== -1 && layout.indexOf("fieldExt=") === -1) { + if (layout.indexOf("fieldKey={\"") !== -1) { return layout.split("fieldKey={\"")[1].split("\"")[0]; } - return keyList.length ? keyList[0] : "data"; + return "data"; } @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @@ -104,7 +90,7 @@ class TreeView extends React.Component { // this document as the data document for the layout. return this.props.document; } - return this.props.dataDoc ? this.props.dataDoc : undefined; + return this.props.dataDoc; } protected createTreeDropTarget = (ele: HTMLDivElement) => { @@ -202,8 +188,8 @@ class TreeView extends React.Component { let headerElements = ( { - this.props.document.treeViewExpandedView = this.treeViewExpandedView === "data" ? "fields" : - this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : "data"; + this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : + this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : this.fieldKey; this._collapsed = false; })}> {this.treeViewExpandedView} @@ -382,17 +368,20 @@ class TreeView extends React.Component { render() { let contentElement: (JSX.Element | null) = null; - let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc)); let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before); if (!this._collapsed) { - if (this.treeViewExpandedView === "data") { - let doc = Cast(this.props.document[this.fieldKey], Doc); + if (this.treeViewExpandedView === this.fieldKey) { + let layout = this.props.document.layout as Doc; + let childDocs = (this.props.dataDoc ? Cast(this.props.dataDoc[this.fieldKey], listSpec(Doc)) : undefined) || + (layout ? Cast(layout[this.fieldKey], listSpec(Doc)) : undefined) || + Cast(this.props.document[this.fieldKey], listSpec(Doc)); contentElement =
    {this.fieldKey === "links" ? this.renderLinks() : - TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} + !childDocs ? (null) : + TreeView.GetChildElements(childDocs as Doc[], this.props.treeViewId, layout, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, + this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
; } else if (this.treeViewExpandedView === "fields") { contentElement =
    @@ -454,6 +443,8 @@ class TreeView extends React.Component { let docList = docs.filter(child => !child.excludeFromLibrary); let rowWidth = () => panelWidth() - 20; return docList.map((child, i) => { + let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); + let indent = i === 0 ? undefined : () => { if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) { let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1]; @@ -470,8 +461,8 @@ class TreeView extends React.Component { return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym](); }; return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); -- cgit v1.2.3-70-g09d2 From 570081063e27498e3de46d90ae49aa4dc3fc5833 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 23:29:30 -0400 Subject: refactored mapping and validation to make more extensible --- src/client/util/DictationManager.ts | 59 ++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 3e130f984..add3f2dd3 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,17 +1,15 @@ -import { string } from "prop-types"; -import { observable, action, autorun } from "mobx"; import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; -import { UndoManager, undoBatch } from "./UndoManager"; +import { undoBatch } from "./UndoManager"; import * as converter from "words-to-numbers"; -import { Doc, Field } from "../../new_fields/Doc"; +import { Doc } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; -import { Docs } from "../documents/Documents"; +import { Docs, DocumentType } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; -import { MainView } from "../views/MainView"; import { listSpec } from "../../new_fields/Schema"; -import { Cast, ToConstructor, ListSpec, CastCtor } from "../../new_fields/Types"; -import { ImageField } from "../../new_fields/URLField"; +import { Cast, CastCtor } from "../../new_fields/Types"; +import { ImageField, AudioField } from "../../new_fields/URLField"; +import { HistogramField } from "../northstar/dash-fields/HistogramField"; namespace CORE { export interface IWindow extends Window { @@ -19,22 +17,37 @@ namespace CORE { } } -export namespace Validators { - - const tryCast = (view: DocumentView, ctor: T) => Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; - - export const isCollectionView: ActionPredicate = (target: DocumentView) => tryCast(target, listSpec(Doc)); - - export const isImageView: ActionPredicate = (target: DocumentView) => tryCast(target, ImageField); +const Mapping = new Map([ + [DocumentType.COL, listSpec(Doc)], + [DocumentType.AUDIO, AudioField], + [DocumentType.IMG, ImageField], + [DocumentType.HIST, HistogramField], + [DocumentType.IMPORT, listSpec(Doc)] +]); + +const tryCast = (view: DocumentView, type: DocumentType) => { + let ctor = Mapping.get(type); + if (!ctor) { + return false; + } + return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; +}; -} +const validate = (target: DocumentView, types: DocumentType[]) => { + for (let type of types) { + if (tryCast(target, type)) { + return true; + } + } + return false; +}; const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegistrationEntry = { action: IndependentAction, validate?: ActionPredicate }; +export type RegistrationEntry = { action: IndependentAction, valid?: DocumentType[] }; export type ActionPredicate = (target: DocumentView) => boolean; -export type RegexEntry = { expression: RegExp, action: DependentAction, validate?: ActionPredicate }; +export type RegexEntry = { expression: RegExp, action: DependentAction, valid?: DocumentType[] }; export default class DictationManager { public static Instance = new DictationManager(); @@ -119,7 +132,7 @@ export default class DictationManager { phrase = this.sanitize(phrase); let entry = RegisteredCommands.Independent.get(phrase); - if (entry && (!entry.validate || entry.validate(target))) { + if (entry && (!entry.valid || validate(target, entry.valid))) { await entry.action(target); return true; } @@ -128,7 +141,7 @@ export default class DictationManager { let regex = entry.expression; let matches = regex.exec(phrase); regex.lastIndex = 0; - if (matches !== null && (!entry.validate || entry.validate(target))) { + if (matches !== null && (!entry.valid || validate(target, entry.valid))) { await entry.action(target, matches); return true; } @@ -147,7 +160,7 @@ export namespace RegisteredCommands { action: (target: DocumentView) => { Doc.GetProto(target.props.Document).data = new List(); }, - validate: Validators.isCollectionView + valid: [DocumentType.COL] }], ["open fields", { @@ -181,7 +194,7 @@ export namespace RegisteredCommands { created && Doc.AddDocToList(dataDoc, fieldKey, created); } }, - validate: Validators.isCollectionView + valid: [DocumentType.COL] }, { @@ -190,7 +203,7 @@ export namespace RegisteredCommands { let mode = CollectionViewType.ValueOf(matches[1]); mode && (target.props.Document.viewType = mode); }, - validate: Validators.isCollectionView + valid: [DocumentType.COL] } ); -- cgit v1.2.3-70-g09d2 From 5db091ba03d5dd13ffb984e85257c16a314d4c42 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 31 Jul 2019 23:31:46 -0400 Subject: naming tweak --- src/client/util/DictationManager.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index add3f2dd3..49afe5371 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -45,9 +45,9 @@ const validate = (target: DocumentView, types: DocumentType[]) => { const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; export type IndependentAction = (target: DocumentView) => any | Promise; export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegistrationEntry = { action: IndependentAction, valid?: DocumentType[] }; +export type RegistrationEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; export type ActionPredicate = (target: DocumentView) => boolean; -export type RegexEntry = { expression: RegExp, action: DependentAction, valid?: DocumentType[] }; +export type RegexEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; export default class DictationManager { public static Instance = new DictationManager(); @@ -132,7 +132,7 @@ export default class DictationManager { phrase = this.sanitize(phrase); let entry = RegisteredCommands.Independent.get(phrase); - if (entry && (!entry.valid || validate(target, entry.valid))) { + if (entry && (!entry.restrictTo || validate(target, entry.restrictTo))) { await entry.action(target); return true; } @@ -141,7 +141,7 @@ export default class DictationManager { let regex = entry.expression; let matches = regex.exec(phrase); regex.lastIndex = 0; - if (matches !== null && (!entry.valid || validate(target, entry.valid))) { + if (matches !== null && (!entry.restrictTo || validate(target, entry.restrictTo))) { await entry.action(target, matches); return true; } @@ -160,7 +160,7 @@ export namespace RegisteredCommands { action: (target: DocumentView) => { Doc.GetProto(target.props.Document).data = new List(); }, - valid: [DocumentType.COL] + restrictTo: [DocumentType.COL] }], ["open fields", { @@ -194,7 +194,7 @@ export namespace RegisteredCommands { created && Doc.AddDocToList(dataDoc, fieldKey, created); } }, - valid: [DocumentType.COL] + restrictTo: [DocumentType.COL] }, { @@ -203,7 +203,7 @@ export namespace RegisteredCommands { let mode = CollectionViewType.ValueOf(matches[1]); mode && (target.props.Document.viewType = mode); }, - valid: [DocumentType.COL] + restrictTo: [DocumentType.COL] } ); -- cgit v1.2.3-70-g09d2 From d4accdd22cabdd78fc7bd76dd7ce3cb59e4b102f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 1 Aug 2019 00:31:50 -0400 Subject: more treeview cleanup and fixes. --- src/client/util/SelectionManager.ts | 1 + src/client/views/DocumentDecorations.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 340 +++++++++------------ .../authentication/models/current_user_utils.ts | 2 +- 4 files changed, 145 insertions(+), 202 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 9efef888d..ee623d082 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,6 +4,7 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; import { InkingControl } from "../views/InkingControl"; +import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; export namespace SelectionManager { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 40f2c3da9..eccfc7c30 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -29,6 +29,7 @@ import { LinkManager } from '../util/LinkManager'; import { ObjectField } from '../../new_fields/ObjectField'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { ImageBox } from './nodes/ImageBox'; +import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -144,7 +145,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get Bounds(): { x: number, y: number, b: number, r: number } { return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { - if (documentView.props.renderDepth === 0) { + if (documentView.props.renderDepth === 0 || + Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) { return bounds; } let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse(); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index fa75c680a..7f5d78313 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -25,7 +25,6 @@ import { CollectionSchemaPreview } from './CollectionSchemaView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); -import { LinkManager } from '../../util/LinkManager'; import { ComputedField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; @@ -67,22 +66,24 @@ library.add(faPlus, faMinus); * Component that takes in a document prop and a boolean whether it's collapsed or not. */ class TreeView extends React.Component { + static loadId = ""; private _header?: React.RefObject = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); - @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.fieldKey); } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } @observable _collapsed: boolean = true; - + @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "fields"); } + @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } + @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @computed get fieldKey() { - let layout = StrCast(this.props.document.layout); - if (layout.indexOf("fieldKey={\"") !== -1) { - return layout.split("fieldKey={\"")[1].split("\"")[0]; - } - return "data"; + let splits = StrCast(this.props.document.layout).split("fieldKey={\""); + return splits.length > 1 ? splits[1].split("\"")[0] : "data"; + } + @computed get childDocs() { + let layout = this.props.document.layout as Doc; + return (this.props.dataDoc ? Cast(this.props.dataDoc[this.fieldKey], listSpec(Doc)) : undefined) || + (layout ? Cast(layout[this.fieldKey], listSpec(Doc)) : undefined) || + Cast(this.props.document[this.fieldKey], listSpec(Doc)); } - - @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @computed get resolvedDataDoc() { if (this.props.dataDoc === undefined && this.props.document.layout instanceof Doc) { // if there is no dataDoc (ie, we're not rendering a template layout), but this document @@ -92,16 +93,30 @@ class TreeView extends React.Component { } return this.props.dataDoc; } + @computed get boundsOfCollectionDocument() { + return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 ? undefined : + Doc.ComputeContentBounds(DocListCast(this.props.document.data)); + } - protected createTreeDropTarget = (ele: HTMLDivElement) => { - this._treedropDisposer && this._treedropDisposer(); - if (ele) { - this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }); + @undoBatch delete = () => this.props.deleteDoc(this.dataDoc); + @undoBatch openRight = () => this.props.addDocTab(this.props.document, undefined, "onRight"); + @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete(); + @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => { + return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); + } + @undoBatch @action remove = (document: Document, key: string): boolean => { + let children = Cast(this.dataDoc[key], listSpec(Doc), []); + if (children.indexOf(document) !== -1) { + children.splice(children.indexOf(document), 1); + return true; } + return false; } - @undoBatch delete = () => this.props.deleteDoc(this.dataDoc); - @undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight"); + protected createTreeDropTarget = (ele: HTMLDivElement) => { + this._treedropDisposer && this._treedropDisposer(); + ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } })); + } onPointerDown = (e: React.PointerEvent) => e.stopPropagation(); onPointerEnter = (e: React.PointerEvent): void => { @@ -130,34 +145,6 @@ class TreeView extends React.Component { e.stopPropagation(); } - @action - remove = (document: Document, key: string): boolean => { - let children = Cast(this.dataDoc[key], listSpec(Doc), []); - if (children.indexOf(document) !== -1) { - children.splice(children.indexOf(document), 1); - return true; - } - return false; - } - - @action - move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => { - return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); - } - @action - indent = () => this.props.addDocument(this.props.document) && this.delete() - - renderBullet() { - let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc)); - let doc = Cast(this.dataDoc[this.fieldKey], Doc); - let isDoc = doc instanceof Doc || docList; - let c; - return
    this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> - {} -
    ; - } - - static loadId = ""; editableView = (key: string, style?: string) => ( { OnTab={() => this.props.indentDocument && this.props.indentDocument()} />) - /** - * Renders the EditableView title element for placement into the tree. - */ - renderTitle() { - let reference = React.createRef(); - let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true); - - let headerElements = ( - { - this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : - this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : this.fieldKey; - this._collapsed = false; - })}> - {this.treeViewExpandedView} - ); - let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : []; - let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : ( -
    - -
    ); - return <> -
    - {this.editableView("title")} - {/* {
    } */} -
    - {headerElements} - {openRight} - ; - } - onWorkspaceContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { @@ -275,39 +225,6 @@ class TreeView extends React.Component { let finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); return finalXf; } - - renderLinks = () => { - let ele: JSX.Element[] = []; - let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this.fieldKey, doc, addBefore, before); - let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document); - groups.forEach((groupLinkDocs, groupType) => { - // let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document)); - let destLinks: Doc[] = []; - groupLinkDocs.forEach((doc) => { - let opp = LinkManager.Instance.getOppositeAnchor(doc, this.props.document); - if (opp) { - destLinks.push(opp); - } - }); - ele.push( -
    -
    {groupType}:
    - { - TreeView.GetChildElements(destLinks, this.props.treeViewId, this.props.document, this.props.dataDoc, "treeviewlink-" + groupType, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth) - } -
    - ); - }); - return ele; - } - - @computed get boundsOfCollectionDocument() { - if (StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1) return undefined; - let layoutDoc = this.props.document; - return Doc.ComputeContentBounds(DocListCast(layoutDoc.data)); - } docWidth = () => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 5)); @@ -323,39 +240,29 @@ class TreeView extends React.Component { })()); } - noOverlays = (doc: Doc) => ({ title: "", caption: "" }); - - expandedField = (doc?: Doc) => { - if (!doc) return
    ; - let realDoc = doc; - + expandedField = (doc: Doc) => { let ids: { [key: string]: string } = {}; - Object.keys(doc).forEach(key => { - if (!(key in ids) && realDoc[key] !== ComputedField.undefined) { - ids[key] = key; - } - }); + doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); let rows: JSX.Element[] = []; for (let key of Object.keys(ids).sort()) { - let contents = realDoc[key] ? realDoc[key] : undefined; + let contents = doc[key]; let contentElement: JSX.Element[] | JSX.Element = []; if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { - let docList = contents; let remDoc = (doc: Doc) => this.remove(doc, key); let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before); - contentElement = key === "links" ? this.renderLinks() : - TreeView.GetChildElements(docList instanceof Doc ? [docList] : DocListCast(docList), this.props.treeViewId, realDoc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); + contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : + DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, + this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); } else { contentElement = Field.toKeyValueString(realDoc, key)} - SetValue={(value: string) => KeyValueBox.SetField(realDoc, key, value)} />; + GetValue={() => Field.toKeyValueString(doc, key)} + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value)} />; } rows.push(
    {key + ":"} @@ -366,59 +273,100 @@ class TreeView extends React.Component { return rows; } - render() { - let contentElement: (JSX.Element | null) = null; - let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before); + noOverlays = (doc: Doc) => ({ title: "", caption: "" }); - if (!this._collapsed) { - if (this.treeViewExpandedView === this.fieldKey) { - let layout = this.props.document.layout as Doc; - let childDocs = (this.props.dataDoc ? Cast(this.props.dataDoc[this.fieldKey], listSpec(Doc)) : undefined) || - (layout ? Cast(layout[this.fieldKey], listSpec(Doc)) : undefined) || - Cast(this.props.document[this.fieldKey], listSpec(Doc)); - contentElement =
      - {this.fieldKey === "links" ? this.renderLinks() : - !childDocs ? (null) : - TreeView.GetChildElements(childDocs as Doc[], this.props.treeViewId, layout, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} -
    ; - } else if (this.treeViewExpandedView === "fields") { - contentElement =
      - {this.expandedField(this.dataDoc)} -
    ; - } else { - let layoutDoc = this.props.document; - contentElement =
    - - -
    ; - } + @computed get renderContent() { + if (this.treeViewExpandedView === this.fieldKey) { + let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before); + return
      + {!this.childDocs ? (null) : + TreeView.GetChildElements(this.childDocs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, + this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, + this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} +
    ; + } else if (this.treeViewExpandedView === "fields") { + return
      + {this.dataDoc ? this.expandedField(this.dataDoc) : (null)} +
    ; + } else { + let layoutDoc = this.props.document; + return
    + + +
    ; } + } + + @computed + get renderBullet() { + return
    this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> + {} +
    ; + } + /** + * Renders the EditableView title element for placement into the tree. + */ + @computed + get renderTitle() { + let reference = React.createRef(); + let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true); + + let headerElements = ( + { + this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : + this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : + this.childDocs ? this.fieldKey : "fields"; + this._collapsed = false; + })}> + {this.treeViewExpandedView} + ); + let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : []; + let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : ( +
    + +
    ); + return <> +
    + {this.editableView("title")} +
    + {headerElements} + {openRight} + ; + } + + render() { return
  • - {this.renderBullet()} - {this.renderTitle()} + {this.renderBullet} + {this.renderTitle}
    - {contentElement} + {this._collapsed ? (null) : this.renderContent}
  • ; @@ -488,7 +436,9 @@ export class CollectionTreeView extends CollectionSubView(Document) { private treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; - @computed get chromeCollapsed() { return this.props.chromeCollapsed; } + @observable static NotifsCol: Opt; + + @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer && this.treedropDisposer(); @@ -520,21 +470,15 @@ export class CollectionTreeView extends CollectionSubView(Document) { ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } } - - @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } - outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); - - - @observable static NotifsCol: Opt; - openNotifsCol = () => { if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) { CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined); } } - @computed get notifsButton() { + + @computed get renderNotifsButton() { const length = CollectionTreeView.NotifsCol ? DocListCast(CollectionTreeView.NotifsCol.data).length : 0; const notifsRef = React.createRef(); const dragNotifs = action(() => CollectionTreeView.NotifsCol!); @@ -550,20 +494,16 @@ export class CollectionTreeView extends CollectionSubView(Document) {
    ; } - @computed get clearButton() { + @computed get renderClearButton() { return
    -
    - -
    +
    ; } - render() { - console.log("DTITLE + " + this.props.Document.title); Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); let dropAction = StrCast(this.props.Document.dropAction) as dropActionType; let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); @@ -588,8 +528,8 @@ export class CollectionTreeView extends CollectionSubView(Document) { TreeView.loadId = doc[Id]; Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true); }} /> - {this.props.Document.workspaceLibrary ? this.notifsButton : (null)} - {this.props.Document.allowClear ? this.clearButton : (null)} + {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)} + {this.props.Document.allowClear ? this.renderClearButton : (null)}
      { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 1c52a3f11..91d7ba87d 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -71,7 +71,7 @@ export class CurrentUserUtils { doc.sidebar = sidebar; } StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library"); - + doc.width = 100; } public static loadCurrentUser() { -- cgit v1.2.3-70-g09d2 From ec224416fc454c7fdbb62943408226c973d8c751 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 1 Aug 2019 02:30:47 -0400 Subject: fixed import ordering issue, allow for multiple targets and valid document types --- src/client/documents/Documents.ts | 45 ++++++++------- src/client/util/DictationManager.ts | 66 ++++++++-------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 --- src/client/views/nodes/DocumentView.tsx | 1 - src/client/views/search/FilterBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 2 +- src/client/views/search/ToggleBar.tsx | 2 +- 7 files changed, 49 insertions(+), 77 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2a1f63d59..3c7de17c8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,3 +1,24 @@ +export enum DocumentType { + NONE = "none", + TEXT = "text", + HIST = "histogram", + IMG = "image", + WEB = "web", + COL = "collection", + KVP = "kvp", + VID = "video", + AUDIO = "audio", + PDF = "pdf", + ICON = "icon", + IMPORT = "import", + LINK = "link", + LINKDOC = "linkdoc", + BUTTON = "button", + TEMPLATE = "template", + EXTENSION = "extension", + YOUTUBE = "youtube", +} + import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; import { HistogramOperation } from "../northstar/operations/HistogramOperation"; @@ -25,14 +46,13 @@ import { OmitKeys, JSONUtils } from "../../Utils"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; -import { Cast, NumCast, StrCast, ToConstructor, InterfaceValue, FieldValue } from "../../new_fields/Types"; +import { Cast, NumCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; -import { RouteStore } from "../../server/RouteStore"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { LinkManager } from "../util/LinkManager"; @@ -44,27 +64,6 @@ import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderFi var requestImageSize = require('../util/request-image-size'); var path = require('path'); -export enum DocumentType { - NONE = "none", - TEXT = "text", - HIST = "histogram", - IMG = "image", - WEB = "web", - COL = "collection", - KVP = "kvp", - VID = "video", - AUDIO = "audio", - PDF = "pdf", - ICON = "icon", - IMPORT = "import", - LINK = "link", - LINKDOC = "linkdoc", - BUTTON = "button", - TEMPLATE = "template", - EXTENSION = "extension", - YOUTUBE = "youtube", -} - export interface DocumentOptions { x?: number; y?: number; diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 49afe5371..b0866a826 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -6,9 +6,9 @@ import { Doc } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs, DocumentType } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; -import { listSpec } from "../../new_fields/Schema"; import { Cast, CastCtor } from "../../new_fields/Types"; -import { ImageField, AudioField } from "../../new_fields/URLField"; +import { listSpec } from "../../new_fields/Schema"; +import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; namespace CORE { @@ -17,7 +17,7 @@ namespace CORE { } } -const Mapping = new Map([ +const ConstructorMap = new Map([ [DocumentType.COL, listSpec(Doc)], [DocumentType.AUDIO, AudioField], [DocumentType.IMG, ImageField], @@ -26,7 +26,7 @@ const Mapping = new Map([ ]); const tryCast = (view: DocumentView, type: DocumentType) => { - let ctor = Mapping.get(type); + let ctor = ConstructorMap.get(type); if (!ctor) { return false; } @@ -82,28 +82,6 @@ export default class DictationManager { }); } - private sanitize = (title: string) => { - return title.replace("...", "").toLowerCase().trim(); - } - - public registerStatic = (keys: Array, action: IndependentAction, filter?: ActionPredicate) => { - let success = true; - keys.forEach(key => { - key = this.sanitize(key); - let existing = RegisteredCommands.Independent.get(key); - if (!existing) { - let unit = { - action: action, - filter: filter - }; - RegisteredCommands.Independent.set(key, unit); - } else { - success = false; - } - }); - return success; - } - public interpretNumber = (number: string) => { let initial = parseInt(number); if (!isNaN(initial)) { @@ -116,34 +94,38 @@ export default class DictationManager { return typeof converted === "string" ? parseInt(converted) : converted; } - public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => { - RegisteredCommands.Dependent.push({ - expression: dynamicKey, - action: action - }); - } - @undoBatch public execute = async (phrase: string) => { - let target = SelectionManager.SelectedDocuments()[0]; - if (!target) { + let targets = SelectionManager.SelectedDocuments(); + if (!targets || !targets.length) { return; } - phrase = this.sanitize(phrase); let entry = RegisteredCommands.Independent.get(phrase); - if (entry && (!entry.restrictTo || validate(target, entry.restrictTo))) { - await entry.action(target); - return true; + if (entry) { + let success = false; + for (let target of targets) { + if (!entry.restrictTo || validate(target, entry.restrictTo)) { + await entry.action(target); + success = true; + } + } + return success; } for (let entry of RegisteredCommands.Dependent) { let regex = entry.expression; let matches = regex.exec(phrase); regex.lastIndex = 0; - if (matches !== null && (!entry.restrictTo || validate(target, entry.restrictTo))) { - await entry.action(target, matches); - return true; + if (matches !== null) { + let success = false; + for (let target of targets) { + if (!entry.restrictTo || validate(target, entry.restrictTo)) { + await entry.action(target, matches); + success = true; + } + } + return success; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 59c77f1c9..fa49e7175 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -123,14 +123,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); } - constructor(props: SubCollectionViewProps) { - super(props); - let fixed = DictationManager.Instance.registerStatic; - fixed(["Unset Fit To Container", "Set Fit To Container"], this.fitToContainer); - fixed(["Arrange contents in grid"], this.arrangeContents); - fixed(["Analyze Strokes"], this.analyzeStrokes); - } - @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 39574db0f..51661d1ae 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,7 +41,6 @@ import { ClientUtils } from '../../util/ClientUtils'; import { EditableView } from '../EditableView'; import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons'; import { DocumentDecorations } from '../DocumentDecorations'; -import { CognitiveServices } from '../../cognitive_services/CognitiveServices'; import DictationManager from '../../util/DictationManager'; import { CollectionViewType } from '../collections/CollectionBaseView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 995ddd5c3..3e8582d61 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -384,7 +384,7 @@ export class FilterBox extends React.Component {
      Collection Filters Active
    : undefined}
- ) + ); } // Useful queries: diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 0390359b3..6fbc92007 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -134,7 +134,7 @@ export class LinkContextMenu extends React.Component {
- ) + ); } } diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx index a30104089..ed5ecd3ba 100644 --- a/src/client/views/search/ToggleBar.tsx +++ b/src/client/views/search/ToggleBar.tsx @@ -59,7 +59,7 @@ export class ToggleBar extends React.Component{ this._forwardTimeline.play(); this._forwardTimeline.reverse(); this.props.handleChange(); - console.log(this.props.getStatus()) + console.log(this.props.getStatus()); } @action.bound -- cgit v1.2.3-70-g09d2 From 810b7898b279069fb8d7cc666333d02e71e23fd2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 1 Aug 2019 02:39:33 -0400 Subject: naming tweak --- src/client/views/GlobalKeyHandler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 59d120974..99345f04e 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -114,15 +114,15 @@ export default class KeyManager { let main = MainView.Instance; main.dictationOverlayVisible = true; main.isListening = true; - let manager = DictationManager.Instance; - let command = await manager.listen(); + let dictation = DictationManager.Instance; + let command = await dictation.listen(); main.isListening = false; if (!command) { break; } command = command.toLowerCase(); main.dictatedPhrase = command; - main.dictationSuccess = await manager.execute(command); + main.dictationSuccess = await dictation.execute(command); main.overlayTimeout = setTimeout(() => { main.dictationOverlayVisible = false; main.dictationSuccess = undefined; -- cgit v1.2.3-70-g09d2 From a4000aa57c9934ff3a68ba17474936836a5f235e Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 1 Aug 2019 10:35:02 -0400 Subject: tweaks --- src/client/views/ContextMenu.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 1 - .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 11 +++++------ src/client/views/nodes/DocumentView.tsx | 6 ------ 4 files changed, 6 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 98025ac31..1bf6e383d 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -42,7 +42,7 @@ export class ContextMenu extends React.Component { return this._items.find(menuItem => { let reference = menuItem.description; toLowerCase && (reference = reference.toLowerCase()); - reference === target; + return reference === target; }); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index eccfc7c30..15471371a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -541,7 +541,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (fixedAspect && (!nwidth || !nheight)) { proto.nativeWidth = nwidth = doc.width || 0; proto.nativeHeight = nheight = doc.height || 0; - proto.ignoreAspect = true; } if (nwidth > 0 && nheight > 0 && !BoolCast(proto.ignoreAspect)) { if (Math.abs(dW) > Math.abs(dH)) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 33a584c6f..29f9b1429 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -131,10 +131,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action drop = (e: Event, de: DragManager.DropEvent) => { let xf = this.getTransform(); + let [xp, yp] = xf.transformPoint(de.x, de.y); if (super.drop(e, de)) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { - let [xp, yp] = xf.transformPoint(de.x, de.y); let x = xp - de.data.xOffset; let y = yp - de.data.yOffset; let dropX = NumCast(de.data.droppedDocuments[0].x); @@ -157,7 +157,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let [xp, yp] = this.getTransform().transformPoint(de.x, de.y); let x = xp - de.data.xOffset; let y = yp - de.data.yOffset; let dropX = NumCast(de.data.dropDocument.x); @@ -224,10 +223,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._pheight / this.zoomScaling()); let panelwidth = panelDim[0]; let panelheight = panelDim[1]; - // if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; - // if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; - // if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; - // if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; + if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; + if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; + if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; + if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; } this.setPan(x - dx, y - dy); this._lastX = e.pageX; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 14f0b5a5a..d4adcfca1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -586,12 +586,6 @@ export class DocumentView extends DocComponent(Docu }, icon: "window-restore" }); cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); - // cm.addItem({ - // description: "Find aliases", event: async () => { - // const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document); - // this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc? - // }, icon: "search" - // }); if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) { cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" }); } -- cgit v1.2.3-70-g09d2 From 6f3d4a7015e15e0523fc194f7f911f6d45259165 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 1 Aug 2019 13:57:59 -0400 Subject: added floating docs --- src/client/util/DragManager.ts | 7 +++- src/client/views/TemplateMenu.tsx | 44 ++++++++++++++++++++- .../collectionFreeForm/CollectionFreeFormView.scss | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 46 +++++++++++++++------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 42 +++++++++----------- 6 files changed, 100 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index abcc3a4e1..0b5c785a4 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -10,6 +10,7 @@ import { LinkManager } from "./LinkManager"; import { SelectionManager } from "./SelectionManager"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; import { DocumentDecorations } from "../views/DocumentDecorations"; +import { NumberLiteralType } from "typescript"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag( @@ -140,6 +141,10 @@ export namespace DragManager { dragHasStarted?: () => void; withoutShiftDrag?: boolean; + + offsetX?: number; + + offsetY?: number; } export interface DragDropDisposer { @@ -423,7 +428,7 @@ export namespace DragManager { lastX = e.pageX; lastY = e.pageY; dragElements.map((dragElement, i) => (dragElement.style.transform = - `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) + `translate(${(xs[i] += moveX) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) ); }; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 1b32f0ddd..c413650f0 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -7,6 +7,10 @@ import { DocumentView } from "./nodes/DocumentView"; import { Template } from "./Templates"; import React = require("react"); import { undoBatch } from "../util/UndoManager"; +import { DocumentManager } from "../util/DocumentManager"; +import { NumCast } from "../../new_fields/Types"; +import { DragManager } from "../util/DragManager"; +import { SelectionManager } from "../util/SelectionManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -35,11 +39,46 @@ export interface TemplateMenuProps { @observer export class TemplateMenu extends React.Component { @observable private _hidden: boolean = true; + dragRef = React.createRef(); constructor(props: TemplateMenuProps) { super(props); } + toggleFloat = (e: React.MouseEvent): void => { + SelectionManager.DeselectAll(); + let topDocView = this.props.docs[0]; + let topDoc = topDocView.props.Document; + let xf = topDocView.props.ScreenToLocalTransform(); + let ex = e.clientX; + let ey = e.clientY; + undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))(); + if (!topDoc.z) { + setTimeout(() => { + let newDocView = DocumentManager.Instance.getDocumentView(topDoc); + if (newDocView) { + let de = new DragManager.DocumentDragData([topDoc], [undefined]); + de.moveDocument = topDocView.props.moveDocument; + let xf = newDocView.ContentDiv!.getBoundingClientRect(); + console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left)); + DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, { + offsetX: (ex - xf.left), offsetY: (ey - xf.top), + handlers: { + dragComplete: () => { }, + }, + hideSource: false + }); + } + }, 10); + } else if (topDocView.props.ContainingCollectionView) { + let collView = topDocView.props.ContainingCollectionView!; + let [sx, sy] = xf.inverse().transformPoint(0, 0); + let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy); + topDoc.x = x; + topDoc.y = y; + } + } + @undoBatch @action toggleTemplate = (event: React.ChangeEvent, template: Template): void => { @@ -89,9 +128,10 @@ export class TemplateMenu extends React.Component { return (
this.toggleTemplateActivity()}>+
-
    +
      {templateMenu} - + +
); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index cca199afa..c4311fa52 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -46,6 +46,7 @@ border-radius: inherit; box-sizing: border-box; position: absolute; + overflow: hidden; .marqueeView { overflow: hidden; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 29f9b1429..9cbc652b9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -77,7 +77,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @computed get contentBounds() { - let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined; + let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; let res = { panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, @@ -98,6 +98,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); + private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1); private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { @@ -131,12 +132,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action drop = (e: Event, de: DragManager.DropEvent) => { let xf = this.getTransform(); + let xfo = this.getTransformOverlay(); let [xp, yp] = xf.transformPoint(de.x, de.y); + let [xpo, ypo] = xfo.transformPoint(de.x, de.y); if (super.drop(e, de)) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { - let x = xp - de.data.xOffset; - let y = yp - de.data.yOffset; + let z = NumCast(de.data.draggedDocuments[0].z); + let x = (z ? xpo : xp) - de.data.xOffset; + let y = (z ? ypo : yp) - de.data.yOffset; let dropX = NumCast(de.data.droppedDocuments[0].x); let dropY = NumCast(de.data.droppedDocuments[0].y); de.data.droppedDocuments.forEach(d => { @@ -292,8 +296,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY; - // this.props.Document.panX = panX; - // this.props.Document.panY = panY; if (this.props.Document.scrollY) { this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym](); } @@ -396,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, - ScreenToLocalTransform: this.getTransform, + ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, selectOnLoad: pair.layout[Id] === this._selectOnLoaded, PanelWidth: pair.layout[WidthSym], @@ -436,23 +438,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } { + getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } { const result = script.script.run(params); if (!result.success) { return {}; } - return result.result === undefined ? {} : result.result; + let doc = params.doc; + return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result; } - private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined { + private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } } | undefined { if (viewDef.type === "text") { const text = Cast(viewDef.text, "string"); const x = Cast(viewDef.x, "number"); const y = Cast(viewDef.y, "number"); + const z = Cast(viewDef.z, "number"); const width = Cast(viewDef.width, "number"); const height = Cast(viewDef.height, "number"); const fontSize = Cast(viewDef.fontSize, "number"); - if ([text, x, y, width, height].some(val => val === undefined)) { + if ([text, x, y, z, width, height].some(val => val === undefined)) { return undefined; } @@ -460,7 +464,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ele:
{text}
, bounds: { x: x!, y: y!, width: width!, height: height! } + }}>{text}
, bounds: { x: x!, y: y!, z: z, width: width!, height: height! } }; } } @@ -472,7 +476,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const script = this.Document.arrangeScript; let state: any = undefined; const docs = this.childDocs; - let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = []; + let elements: { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } }[] = []; if (initScript) { const initResult = initScript.script.run({ docs, collection: this.Document }); if (initResult.success) { @@ -494,13 +498,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let minim = BoolCast(doc.isMinimized); if (minim === undefined || !minim) { const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : - { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; + { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; state = pos.state === undefined ? state : pos.state; prev.push({ ele: , - bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined + bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined }); } } @@ -514,8 +518,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed.struct get views() { - return this.elements.map(ele => ele.ele); + return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } + @computed.struct + get overlayViews() { + return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); + } + @action onCursorMove = (e: React.PointerEvent) => { @@ -603,6 +612,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { , ...this.views ] + private overlayChildViews = () => { + console.log(this.overlayViews.length); + return [ + ...this.overlayViews + ]; + } public static AddCustomLayout(doc: Doc, dataKey: string): () => void { return () => { @@ -651,6 +666,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { + {this.overlayChildViews()} ); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7ffd760e0..3b6c443c2 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -77,6 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent; -- cgit v1.2.3-70-g09d2 From 46843c105c5fb3dfed3bab9283ae4b2917eade4e Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 1 Aug 2019 15:20:40 -0400 Subject: Fixed Doc.MakeCopy promise stuff --- src/client/documents/Documents.ts | 4 ++++ src/new_fields/Doc.ts | 12 +++++++----- src/new_fields/Proxy.ts | 29 +++++++++++++++++++++++++++++ src/new_fields/ScriptField.ts | 12 +++++++----- src/new_fields/util.ts | 5 +---- 5 files changed, 48 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2a1f63d59..63b4d4e32 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -41,6 +41,8 @@ import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox"; import { Scripting } from "../util/Scripting"; import { ButtonBox } from "../views/nodes/ButtonBox"; import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../new_fields/ScriptField"; +import { ProxyField } from "../../new_fields/Proxy"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -193,6 +195,8 @@ export namespace Docs { * haven't been initialized, the newly initialized prototype document. */ export async function initialize(): Promise { + ProxyField.initPlugin(); + ComputedField.initPlugin(); // non-guid string ids for each document prototype let prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); // fetch the actual prototype documents from the server diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 84b8589dd..6e2c5e697 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -12,7 +12,7 @@ import { scriptingGlobal } from "../client/util/Scripting"; import { List } from "./List"; import { DocumentType } from "../client/documents/Documents"; import { ComputedField } from "./ScriptField"; -import { PrefetchProxy } from "./Proxy"; +import { PrefetchProxy, ProxyField } from "./Proxy"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -420,7 +420,7 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc { const copy = new Doc; Object.keys(doc).forEach(key => { - const field = doc[key]; + const field = ProxyField.WithoutProxy(() => doc[key]); if (key === "proto" && copyProto) { if (field instanceof Doc) { copy[key] = Doc.MakeCopy(field); @@ -431,7 +431,7 @@ export namespace Doc { } else if (field instanceof ObjectField) { copy[key] = ObjectField.MakeCopy(field); } else if (field instanceof Promise) { - field.then(f => (copy[key] === undefined) && (copy[key] = f)); //TODO what should we do here? + debugger; //This shouldn't happend... } else { copy[key] = field; } @@ -525,8 +525,10 @@ export namespace Doc { } export function UseDetailLayout(d: Doc) { runInAction(async () => { - let detailLayout1 = await PromiseValue(d.detailedLayout); - let detailLayout = await PromiseValue(d.detailedLayout); + const dl1 = d.detailedLayout; + let detailLayout1 = await PromiseValue(dl1); + const dl2 = d.detailedLayout; + let detailLayout = await PromiseValue(dl2); if (detailLayout) { d.layout = detailLayout; d.nativeWidth = d.nativeHeight = undefined; diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts index b3e8d6467..c6292e37c 100644 --- a/src/new_fields/Proxy.ts +++ b/src/new_fields/Proxy.ts @@ -7,6 +7,7 @@ import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { Id, Copy, ToScriptString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; +import { Plugins } from "./util"; @Deserializable("proxy") export class ProxyField extends ObjectField { @@ -68,6 +69,34 @@ export class ProxyField extends ObjectField { } } +export namespace ProxyField { + let useProxy = true; + export function DisableProxyFields() { + useProxy = false; + } + + export function EnableProxyFields() { + useProxy = true; + } + + export function WithoutProxy(fn: () => T) { + DisableProxyFields(); + try { + return fn(); + } finally { + EnableProxyFields(); + } + } + + export function initPlugin() { + Plugins.addGetterPlugin((doc, _, value) => { + if (useProxy && value instanceof ProxyField) { + return { value: value.value() }; + } + }); + } +} + function prefetchValue(proxy: PrefetchProxy) { return proxy.value() as any; } diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index 6d52525b8..83fb52d07 100644 --- a/src/new_fields/ScriptField.ts +++ b/src/new_fields/ScriptField.ts @@ -137,9 +137,11 @@ export namespace ComputedField { } } - Plugins.addGetterPlugin((doc, _, value) => { - if (useComputed && value instanceof ComputedField) { - return { value: value.value(doc), shouldReturn: true }; - } - }); + export function initPlugin() { + Plugins.addGetterPlugin((doc, _, value) => { + if (useComputed && value instanceof ComputedField) { + return { value: value.value(doc), shouldReturn: true }; + } + }); + } } \ No newline at end of file diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 2ebfb9e71..c6f693f7f 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -14,7 +14,7 @@ function _readOnlySetter(): never { export interface GetterResult { value: FieldResult; - shouldReturn: boolean; + shouldReturn?: boolean; } export type GetterPlugin = (receiver: any, prop: string | number, currentValue: any) => GetterResult | undefined; const getterPlugins: GetterPlugin[] = []; @@ -103,9 +103,6 @@ export function getter(target: any, prop: string | symbol | number, receiver: an function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { receiver = receiver || target[SelfProxy]; let field = target.__fields[prop]; - if (field instanceof ProxyField) { - return field.value(); - } for (const plugin of getterPlugins) { const res = plugin(receiver, prop, field); if (res === undefined) continue; -- cgit v1.2.3-70-g09d2 From 4f09fdb59b71cc945c6af9ff25ea164e7f9d5f13 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 1 Aug 2019 15:39:10 -0400 Subject: Cleaned up and fixed up GetRefFields --- src/client/DocServer.ts | 56 ++++++++++------------ src/client/util/SerializationHelper.ts | 4 +- src/debug/Test.tsx | 88 +++++++++------------------------- 3 files changed, 49 insertions(+), 99 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index fc39fa364..87a87be92 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -124,13 +124,12 @@ export namespace DocServer { // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. const deserializeField = getSerializedField.then(async fieldJson => { // deserialize - const field = await SerializationHelper.Deserialize(fieldJson, val => { - if (val !== undefined) { - _cache[id] = val; - } else { - delete _cache[id]; - } - }); + const field = await SerializationHelper.Deserialize(fieldJson); + if (field !== undefined) { + _cache[id] = field; + } else { + delete _cache[id]; + } return field; // either way, overwrite or delete any promises cached at this id (that we inserted as flags // to indicate that the field was in the process of being fetched). Now everything @@ -214,36 +213,37 @@ export namespace DocServer { // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. const deserializeFields = getSerializedFields.then(async fields => { const fieldMap: { [id: string]: RefField } = {}; - // const protosToLoad: any = []; - const proms: Promise[] = []; + const proms: Promise[] = []; for (const field of fields) { if (field !== undefined) { // deserialize - let prom = SerializationHelper.Deserialize(field, val => { - if (val !== undefined) { - _cache[field.id] = field; + let prom = SerializationHelper.Deserialize(field).then(deserialized => { + fieldMap[field.id] = deserialized; + + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; } else { delete _cache[field.id]; } - }).then(deserialized => fieldMap[field.id] = deserialized); - proms.push(prom); + return deserialized; + }); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + _cache[field.id] = prom; // adds to a list of promises that will be awaited asynchronously - // protosToLoad.push(deserialized.proto); + proms.push(prom); } } await Promise.all(proms); - // this actually handles the loading of prototypes - // await Promise.all(protosToLoad); return fieldMap; }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - requestedIds.forEach(id => _cache[id] = deserializeFields.then(fields => fields[id])); - // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose // prototype documents, if any, have also been fetched and cached. const fields = await deserializeFields; @@ -253,14 +253,6 @@ export namespace DocServer { // id to the soon-to-be-returned field mapping. requestedIds.forEach(id => { const field = fields[id]; - // either way, overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (field !== undefined) { - _cache[id] = field; - } else { - delete _cache[id]; - } map[id] = field; }); diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 13302be21..ff048f647 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -34,7 +34,7 @@ export namespace SerializationHelper { return json; } - export async function Deserialize(obj: any, cb: (val: any) => void = emptyFunction): Promise { + export async function Deserialize(obj: any): Promise { if (obj === undefined || obj === null) { return undefined; } @@ -57,7 +57,7 @@ export namespace SerializationHelper { } const type = serializationTypes[obj.__type]; - const value = await new Promise(res => cb(deserialize(type.ctor, obj, (err, result) => res(result)))); + const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result))); if (type.afterDeserialize) { await type.afterDeserialize(value); } diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index 0dca4b4b1..79f87f4ac 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -1,81 +1,39 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { SerializationHelper } from '../client/util/SerializationHelper'; -import { createSchema, makeInterface, makeStrictInterface, listSpec } from '../new_fields/Schema'; -import { ImageField } from '../new_fields/URLField'; +import { DocServer } from '../client/DocServer'; import { Doc } from '../new_fields/Doc'; -import { List } from '../new_fields/List'; - -const schema1 = createSchema({ - hello: "number", - test: "string", - fields: "boolean", - url: ImageField, - testDoc: Doc -}); - -type TestDoc = makeInterface<[typeof schema1]>; -const TestDoc: (doc?: Doc) => TestDoc = makeInterface(schema1); - -const schema2 = createSchema({ - hello: ImageField, - test: "boolean", - fields: listSpec("number"), - url: "number", - testDoc: ImageField -}); - -const Test2Doc = makeStrictInterface(schema2); -type Test2Doc = makeStrictInterface; - -const assert = (bool: boolean) => { - if (!bool) throw new Error(); -}; +const protoId = "protoDoc"; +const delegateId = "delegateDoc"; class Test extends React.Component { - onClick = () => { - const url = new ImageField(new URL("http://google.com")); - const doc = new Doc(); - const doc2 = new Doc(); - doc.hello = 5; - doc.fields = "test"; - doc.test = "hello doc"; - doc.url = url; - //doc.testDoc = doc2; - + onCreateClick = () => { + const proto = new Doc(protoId, true); + const delegate = Doc.MakeDelegate(proto, delegateId); + } - const test1: TestDoc = TestDoc(doc); - assert(test1.hello === 5); - assert(test1.fields === undefined); - assert(test1.test === "hello doc"); - assert(test1.url === url); - assert(test1.testDoc === doc2); - test1.myField = 20; - assert(test1.myField === 20); + onReadClick = async () => { + console.log("reading"); + const docs = await DocServer.GetRefFields([delegateId, protoId]); + console.log("done"); + console.log(docs); + } - const test2: Test2Doc = Test2Doc(doc); - assert(test2.hello === undefined); - // assert(test2.fields === "test"); - assert(test2.test === undefined); - assert(test2.url === undefined); - assert(test2.testDoc === undefined); - test2.url = 35; - assert(test2.url === 35); - const l = new List(); - //TODO push, and other array functions don't go through the proxy - l.push(doc2); - //TODO currently length, and any other string fields will get serialized - doc.list = l; - console.log(l.slice()); + onDeleteClick = () => { + DocServer.DeleteDocuments([protoId, delegateId]); } render() { - return
- {/* */} -
; + return ( +
+ + + +
+ ); } } +DocServer.init(window.location.protocol, window.location.hostname, 4321, "test"); ReactDOM.render( , document.getElementById('root') -- cgit v1.2.3-70-g09d2 From 65f23d993c091ee7fa61becea85b5f60ec2e9104 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 1 Aug 2019 15:44:22 -0400 Subject: added dragging of clusters. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 37 +++++++++++++++++----- src/client/views/nodes/DocumentView.tsx | 3 +- 2 files changed, 31 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8cac60edb..12e5924b4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -131,15 +131,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { r2: { left: number, top: number, width: number, height: number }) { return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); } + _groupingBorder = 150; bounsdSelect(doc: Doc, doc2: Doc) { - var x2 = NumCast(doc2.x) - 25; - var y2 = NumCast(doc2.y) - 25; - var w2 = NumCast(doc2.width) + 25; - var h2 = NumCast(doc2.height) + 25; - var x = NumCast(doc.x) - 25; - var y = NumCast(doc.y) - 25; - var w = NumCast(doc.width) + 25; - var h = NumCast(doc.height) + 25; + var x2 = NumCast(doc2.x) - this._groupingBorder; + var y2 = NumCast(doc2.y) - this._groupingBorder; + var w2 = NumCast(doc2.width) + this._groupingBorder; + var h2 = NumCast(doc2.height) + this._groupingBorder; + var x = NumCast(doc.x) - this._groupingBorder; + var y = NumCast(doc.y) - this._groupingBorder; + var w = NumCast(doc.width) + this._groupingBorder; + var h = NumCast(doc.height) + this._groupingBorder; if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) { return true; } @@ -242,6 +243,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble) { + let probe = this.getTransform().transformPoint(e.clientX, e.clientY); + let cluster = this.childDocs.reduce((cluster, cd) => { + let cx = NumCast(cd.x) - this._groupingBorder; + let cy = NumCast(cd.y) - this._groupingBorder; + let cw = NumCast(cd.width) + this._groupingBorder; + let ch = NumCast(cd.height) + this._groupingBorder; + if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) + return NumCast(cd.cluster); + return cluster; + }, -1); + if (cluster !== -1) { + let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + this.selectDocuments(eles); + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(v => v.ContentDiv!), new DragManager.DocumentDragData(eles, eles.map(d => undefined)), e.clientX, e.clientY); + e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers + e.preventDefault(); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + return; + } let x = this.Document.panX || 0; let y = this.Document.panY || 0; let docs = this.childDocs || []; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c5cf2e23a..fa1e824f8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -695,6 +695,7 @@ export class DocumentView extends DocComponent(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("(Docu color: foregroundColor, outlineColor: "maroon", outlineStyle: "dashed", - boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`, + boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`, outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? -- cgit v1.2.3-70-g09d2 From c48345b74fadc558062ced96591f041eb4f2729d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 1 Aug 2019 15:55:07 -0400 Subject: cognitive services --- src/client/cognitive_services/CognitiveServices.ts | 32 ++++++++++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 8 ++++-- 3 files changed, 27 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index c118d91d3..08fcb4883 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -7,9 +7,9 @@ import { Utils } from "../../Utils"; import { InkData } from "../../new_fields/InkField"; import { UndoManager } from "../util/UndoManager"; -type APIManager = { converter: BodyConverter, requester: RequestExecutor, analyzer: AnalysisApplier }; +type APIManager = { converter: BodyConverter, requester: RequestExecutor }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise; -type AnalysisApplier = (target: Doc, relevantKeys: string[], ...args: any) => any; +type AnalysisApplier = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any; type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; @@ -38,7 +38,7 @@ export enum Confidence { */ export namespace CognitiveServices { - const ExecuteQuery = async (service: Service, manager: APIManager, data: D): Promise> => { + const ExecuteQuery = async (service: Service, manager: APIManager, data: D): Promise => { return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => { let apiKey = await response.text(); if (!apiKey) { @@ -46,7 +46,7 @@ export namespace CognitiveServices { return undefined; } - let results: Opt; + let results: any; try { results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); } catch { @@ -99,7 +99,11 @@ export namespace CognitiveServices { return request.post(options); }, - analyzer: async (target: Doc, keys: string[], url: string, service: Service, converter: Converter) => { + }; + + export namespace Appliers { + + export const ProcessImage: AnalysisApplier = async (target: Doc, keys: string[], url: string, service: Service, converter: Converter) => { let batch = UndoManager.StartBatch("Image Analysis"); let storageKey = keys[0]; @@ -107,7 +111,7 @@ export namespace CognitiveServices { return; } let toStore: any; - let results = await ExecuteQuery(service, Manager, url); + let results = await ExecuteQuery(service, Manager, url); if (!results) { toStore = "Cognitive Services could not process the given image URL."; } else { @@ -120,9 +124,9 @@ export namespace CognitiveServices { target[storageKey] = toStore; batch.end(); - } + }; - }; + } export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle }; @@ -179,10 +183,14 @@ export namespace CognitiveServices { return new Promise(promisified); }, - analyzer: async (target: Doc, keys: string[], inkData: InkData) => { + }; + + export namespace Appliers { + + export const ConcatenateHandwriting: AnalysisApplier = async (target: Doc, keys: string[], inkData: InkData) => { let batch = UndoManager.StartBatch("Ink Analysis"); - let results = await ExecuteQuery(Service.Handwriting, Manager, inkData); + let results = await ExecuteQuery(Service.Handwriting, Manager, inkData); if (results) { results.recognitionUnits && (results = results.recognitionUnits); target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis"); @@ -192,9 +200,9 @@ export namespace CognitiveServices { } batch.end(); - } + }; - }; + } export interface AzureStrokeData { id: number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index fa49e7175..9535a6f78 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -555,7 +555,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return; } let relevantKeys = ["inkAnalysis", "handwriting"]; - CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData); + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData); } onContextMenu = (e: React.MouseEvent) => { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9a0615d68..879a91fa1 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -237,7 +237,9 @@ export class ImageBox extends DocComponent(ImageD results.map((face: CognitiveServices.Image.Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!)); return faceDocs; }; - CognitiveServices.Image.Manager.analyzer(this.extensionDoc, ["faces"], this.url, Service.Face, converter); + if (this.url) { + CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["faces"], this.url, Service.Face, converter); + } } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { @@ -256,7 +258,9 @@ export class ImageBox extends DocComponent(ImageD tagDoc.confidence = threshold; return tagDoc; }; - CognitiveServices.Image.Manager.analyzer(this.extensionDoc, ["generatedTagsDoc"], this.url, Service.ComputerVision, converter); + if (this.url) { + CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["generatedTagsDoc"], this.url, Service.ComputerVision, converter); + } } @action -- cgit v1.2.3-70-g09d2 From 64d25bd7a92daa4a8b9ee69f0eabd88e5cbeeca5 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 1 Aug 2019 16:04:43 -0400 Subject: cleaned up detaillayout for pivot viewer. --- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/new_fields/Doc.ts | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9cbc652b9..90f67c54e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -456,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const width = Cast(viewDef.width, "number"); const height = Cast(viewDef.height, "number"); const fontSize = Cast(viewDef.fontSize, "number"); - if ([text, x, y, z, width, height].some(val => val === undefined)) { + if ([text, x, y, width, height].some(val => val === undefined)) { return undefined; } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6e2c5e697..b70951040 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -525,19 +525,14 @@ export namespace Doc { } export function UseDetailLayout(d: Doc) { runInAction(async () => { - const dl1 = d.detailedLayout; - let detailLayout1 = await PromiseValue(dl1); - const dl2 = d.detailedLayout; - let detailLayout = await PromiseValue(dl2); + let detailLayout = await d.detailedLayout; if (detailLayout) { d.layout = detailLayout; d.nativeWidth = d.nativeHeight = undefined; if (detailLayout instanceof Doc) { let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc; d.layout = delegDetailLayout; - let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout); - let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout); - delegDetailLayout.layout = subDetailLayout; + delegDetailLayout.layout = await delegDetailLayout.detailedLayout; } } }); -- cgit v1.2.3-70-g09d2 From 40db4e50aaef7e012f652c8637e101b5a1a99d47 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 1 Aug 2019 16:21:29 -0400 Subject: fixed errors. --- src/client/views/TemplateMenu.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index c413650f0..e654a0644 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -39,7 +39,7 @@ export interface TemplateMenuProps { @observer export class TemplateMenu extends React.Component { @observable private _hidden: boolean = true; - dragRef = React.createRef(); + dragRef = React.createRef(); constructor(props: TemplateMenuProps) { super(props); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9a0615d68..17dc4184a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,42 +1,39 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faImage, faFileAudio, faPaintBrush, faAsterisk } from '@fortawesome/free-solid-svg-icons'; -import { action, observable, computed, runInAction } from 'mobx'; +import { faEye } from '@fortawesome/free-regular-svg-icons'; +import { faAsterisk, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from "mobx-react"; import Lightbox from 'react-image-lightbox'; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { Doc, HeightSym, WidthSym, DocListCast } from '../../../new_fields/Doc'; +import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc'; import { List } from '../../../new_fields/List'; import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; -import { Cast, FieldValue, NumCast, StrCast, BoolCast } from '../../../new_fields/Types'; -import { ImageField, AudioField } from '../../../new_fields/URLField'; +import { ComputedField } from '../../../new_fields/ScriptField'; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types'; +import { AudioField, ImageField } from '../../../new_fields/URLField'; +import { RouteStore } from '../../../server/RouteStore'; import { Utils } from '../../../Utils'; +import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; +import { CompileScript } from '../../util/Scripting'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from '../DocComponent'; import { InkingControl } from '../InkingControl'; import { positionSchema } from './DocumentView'; +import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { RouteStore } from '../../../server/RouteStore'; -import { Docs, DocumentType } from '../../documents/Documents'; -import { DocServer } from '../../DocServer'; -import { Font } from '@react-pdf/renderer'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CognitiveServices, Service, Tag, Confidence } from '../../cognitive_services/CognitiveServices'; -import FaceRectangles from './FaceRectangles'; -import { faEye } from '@fortawesome/free-regular-svg-icons'; -import { ComputedField } from '../../../new_fields/ScriptField'; -import { CompileScript } from '../../util/Scripting'; -import { thisExpression } from 'babel-types'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); -library.add(faImage, faEye, faPaintBrush); +library.add(faImage, faEye as any, faPaintBrush); library.add(faFileAudio, faAsterisk); -- cgit v1.2.3-70-g09d2 From e0fb04a7748666a41a7cd500dcdd23027ef12b6f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 2 Aug 2019 12:56:37 -0400 Subject: links --- src/client/documents/Documents.ts | 7 +++++- src/client/util/LinkManager.ts | 13 ++++++++-- .../views/collections/CollectionTreeView.tsx | 28 +++++++++++++++------- src/client/views/nodes/KeyValuePair.tsx | 17 ++++++------- 4 files changed, 43 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 63b4d4e32..818383765 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -38,7 +38,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie import { LinkManager } from "../util/LinkManager"; import { DocumentManager } from "../util/DocumentManager"; import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox"; -import { Scripting } from "../util/Scripting"; +import { Scripting, CompileScript } from "../util/Scripting"; import { ButtonBox } from "../views/nodes/ButtonBox"; import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderField"; import { ComputedField } from "../../new_fields/ScriptField"; @@ -623,6 +623,11 @@ export namespace DocUtils { LinkManager.Instance.addLink(linkDoc); + let script = `return links(self)};`; + let computed = CompileScript(script, { params: { this: "Doc" }, capturedVariables: { self: source }, typecheck: false }); + computed.compiled && (Doc.GetProto(source).links = new ComputedField(computed)); + computed.compiled && (Doc.GetProto(target).links = new ComputedField(computed)); + }, "make link"); return linkDoc; } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index a647f22c1..448a8e9cf 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -6,6 +6,7 @@ import { List } from "../../new_fields/List"; import { Id } from "../../new_fields/FieldSymbols"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import { Docs } from "../documents/Documents"; +import { Scripting } from "./Scripting"; /* @@ -42,7 +43,12 @@ export class LinkManager { } public getAllLinks(): Doc[] { - return LinkManager.Instance.LinkManagerDoc ? LinkManager.Instance.LinkManagerDoc.allLinks ? DocListCast(LinkManager.Instance.LinkManagerDoc.allLinks) : [] : []; + let ldoc = LinkManager.Instance.LinkManagerDoc; + if (ldoc) { + let docs = DocListCast(ldoc.allLinks); + return docs; + } + return []; } public addLink(linkDoc: Doc): boolean { @@ -242,4 +248,7 @@ export class LinkManager { return Cast(linkDoc.anchor1, Doc, null); } } -} \ No newline at end of file +} +Scripting.addGlobal(function links(doc: any) { + return new List(LinkManager.Instance.getAllRelatedLinks(doc)); +}); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 7f5d78313..02b2583cd 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -27,6 +27,7 @@ import "./CollectionTreeView.scss"; import React = require("react"); import { ComputedField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; +import { exportNamedDeclaration } from 'babel-types'; export interface TreeViewProps { @@ -79,11 +80,17 @@ class TreeView extends React.Component { return splits.length > 1 ? splits[1].split("\"")[0] : "data"; } @computed get childDocs() { - let layout = this.props.document.layout as Doc; + let layout = this.props.document.layout instanceof Doc ? this.props.document.layout : undefined; return (this.props.dataDoc ? Cast(this.props.dataDoc[this.fieldKey], listSpec(Doc)) : undefined) || (layout ? Cast(layout[this.fieldKey], listSpec(Doc)) : undefined) || Cast(this.props.document[this.fieldKey], listSpec(Doc)); } + @computed get childLinks() { + let layout = this.props.document.layout instanceof Doc ? this.props.document.layout : undefined; + return (this.props.dataDoc ? Cast(this.props.dataDoc.links, listSpec(Doc)) : undefined) || + (layout instanceof Doc ? Cast(layout.links, listSpec(Doc)) : undefined) || + Cast(this.props.document.links, listSpec(Doc)); + } @computed get resolvedDataDoc() { if (this.props.dataDoc === undefined && this.props.document.layout instanceof Doc) { // if there is no dataDoc (ie, we're not rendering a template layout), but this document @@ -276,13 +283,15 @@ class TreeView extends React.Component { noOverlays = (doc: Doc) => ({ title: "", caption: "" }); @computed get renderContent() { - if (this.treeViewExpandedView === this.fieldKey) { - let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before); - return
    - {!this.childDocs ? (null) : - TreeView.GetChildElements(this.childDocs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, - this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, + const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; + if (expandKey !== undefined) { + let remDoc = (doc: Doc) => this.remove(doc, expandKey); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before); + let docs = expandKey === "links" ? this.childLinks : this.childDocs; + return
      + {!docs ? (null) : + TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, + this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
    ; @@ -334,7 +343,8 @@ class TreeView extends React.Component { onPointerDown={action(() => { this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : - this.childDocs ? this.fieldKey : "fields"; + this.treeViewExpandedView === "layout" && this.props.document.links ? "links" : + this.childDocs ? this.fieldKey : "fields"; this._collapsed = false; })}> {this.treeViewExpandedView} diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 3775f0f47..534a42efc 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,22 +1,19 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { emptyFunction, returnFalse, returnZero, returnTrue, returnOne } from '../../../Utils'; -import { CompileScript, CompiledScript, ScriptOptions } from "../../util/Scripting"; +import { Doc, Field } from '../../../new_fields/Doc'; +import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; +import { undoBatch } from '../../util/UndoManager'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { ContextMenu } from '../ContextMenu'; import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from './FieldView'; +import { KeyValueBox } from './KeyValueBox'; import "./KeyValueBox.scss"; import "./KeyValuePair.scss"; import React = require("react"); -import { Doc, Opt, Field } from '../../../new_fields/Doc'; -import { FieldValue } from '../../../new_fields/Types'; -import { KeyValueBox } from './KeyValueBox'; -import { DragManager, SetupDrag } from '../../util/DragManager'; -import { ContextMenu } from '../ContextMenu'; -import { Docs } from '../../documents/Documents'; -import { CollectionDockingView } from '../collections/CollectionDockingView'; -import { undoBatch } from '../../util/UndoManager'; // Represents one row in a key value plane -- cgit v1.2.3-70-g09d2 From e7e0c506121c3074447df2fd6556fff7b2990d2a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 2 Aug 2019 14:44:29 -0400 Subject: fixed loading of link compute field --- src/client/documents/Documents.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 818383765..17e474e0e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -623,11 +623,10 @@ export namespace DocUtils { LinkManager.Instance.addLink(linkDoc); - let script = `return links(self)};`; - let computed = CompileScript(script, { params: { this: "Doc" }, capturedVariables: { self: source }, typecheck: false }); + let script = `return links(this)};`; + let computed = CompileScript(script, { params: { this: "Doc" }, typecheck: false }); computed.compiled && (Doc.GetProto(source).links = new ComputedField(computed)); computed.compiled && (Doc.GetProto(target).links = new ComputedField(computed)); - }, "make link"); return linkDoc; } -- cgit v1.2.3-70-g09d2 From 8cd098ab0e348c614640bc0971f3859161b8c2b4 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 2 Aug 2019 16:39:03 -0400 Subject: fixed drag interactions with groups. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 4 ++-- 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 12e5924b4..3ff816c46 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -131,7 +131,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { r2: { left: number, top: number, width: number, height: number }) { return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); } - _groupingBorder = 150; + _groupingBorder = 100; bounsdSelect(doc: Doc, doc2: Doc) { var x2 = NumCast(doc2.x) - this._groupingBorder; var y2 = NumCast(doc2.y) - this._groupingBorder; @@ -256,7 +256,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (cluster !== -1) { let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); this.selectDocuments(eles); - DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(v => v.ContentDiv!), new DragManager.DocumentDragData(eles, eles.map(d => undefined)), e.clientX, e.clientY); + let clusterDocs = SelectionManager.SelectedDocuments(); + SelectionManager.DeselectAll(); + let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); + de.moveDocument = this.props.moveDocument; + const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); + const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); + de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; + de.xOffset = xoff; + de.yOffset = yoff; + DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { + handlers: { + dragComplete: action(emptyFunction) + }, + hideSource: !de.dropAction + }); e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fa1e824f8..d79b58a7b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -695,7 +695,7 @@ export class DocumentView extends DocComponent(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("(Docu color: foregroundColor, outlineColor: "maroon", outlineStyle: "dashed", - boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`, + boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`, outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? -- cgit v1.2.3-70-g09d2 From df93a00ed3047abeee90f3ba4858cf7339ce046b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 2 Aug 2019 17:14:42 -0400 Subject: removed proto from links --- src/client/documents/Documents.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2a1f63d59..5d1b6b382 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -599,9 +599,8 @@ export namespace DocUtils { let linkDoc: Doc | undefined; UndoManager.RunInBatch(() => { - linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: "100%" }); - linkDoc.type = DocumentType.LINK; - let linkDocProto = Doc.GetProto(linkDoc); + let linkDocProto = new Doc(); + linkDocProto.type = DocumentType.LINK; linkDocProto.targetContext = targetContext; linkDocProto.sourceContext = sourceContext; @@ -617,7 +616,7 @@ export namespace DocUtils { linkDocProto.anchor2Page = target.curPage; linkDocProto.anchor2Groups = new List([]); - LinkManager.Instance.addLink(linkDoc); + LinkManager.Instance.addLink(linkDocProto); }, "make link"); return linkDoc; -- cgit v1.2.3-70-g09d2 From fe9017c3cdc2481f4bcce40dcc1514a6f7af37dc Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 2 Aug 2019 18:21:23 -0400 Subject: improvements to grouping with backgrounds --- .../views/collections/CollectionBaseView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 68 ++++++++++++---------- src/client/views/nodes/DocumentView.tsx | 9 ++- 3 files changed, 44 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index c595a4c56..6801b94fd 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -147,7 +147,7 @@ export class CollectionBaseView extends React.Component {
    diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 232ac22c9..bc1aee159 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -108,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); this.bringToFront(newBox); + this.updateClusters(); return true; } private selectDocuments = (docs: Doc[]) => { @@ -175,34 +176,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.bringToFront(d); }); - let sets: (Doc[])[] = [] - this.childDocs.map(c => { - let included = [] - for (let i = 0; i < sets.length; i++) { - for (let j = 0; j < sets[i].length; j++) { - if (this.bounsdSelect(c, sets[i][j])) { - included.push(i); - break; - } - } - } - if (included.length === 0) - sets.push([c]); - else if (included.length === 1) - sets[included[0]].push(c); - else { - sets[included[0]].push(c); - for (let s = 1; s < included.length; s++) { - sets[included[0]].push(...sets[included[s]]); - sets[included[s]].length = 0; - } - } - }); - for (let s = 0; s < sets.length; s++) { - for (let i = 0; i < sets[s].length; i++) { - Doc.GetProto(sets[s][i]).cluster = s; - } - } + this.updateClusters(); } } else if (de.data instanceof DragManager.AnnotationDragData) { @@ -223,6 +197,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } + @action + updateClusters() { + let sets: (Doc[])[] = [] + this.childDocs.map(c => { + let included = [] + for (let i = 0; i < sets.length; i++) { + for (let j = 0; j < sets[i].length; j++) { + if (this.bounsdSelect(c, sets[i][j])) { + included.push(i); + break; + } + } + } + if (included.length === 0) + sets.push([c]); + else if (included.length === 1) + sets[included[0]].push(c); + else { + sets[included[0]].push(c); + for (let s = 1; s < included.length; s++) { + sets[included[0]].push(...sets[included[s]]); + sets[included[s]].length = 0; + } + } + }); + for (let s = 0; s < sets.length; s++) { + for (let i = 0; i < sets[s].length; i++) { + Doc.GetProto(sets[s][i]).cluster = s; + } + } + } + @action onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { @@ -247,8 +253,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let cluster = this.childDocs.reduce((cluster, cd) => { let cx = NumCast(cd.x) - this._groupingBorder; let cy = NumCast(cd.y) - this._groupingBorder; - let cw = NumCast(cd.width) + this._groupingBorder; - let ch = NumCast(cd.height) + this._groupingBorder; + let cw = NumCast(cd.width) + 2 * this._groupingBorder; + let ch = NumCast(cd.height) + 2 * this._groupingBorder; if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) return NumCast(cd.cluster); return cluster; @@ -392,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack) { + if (sendToBack || doc.isBackground) { doc.zIndex = 0; return; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d79b58a7b..16ae5471d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -695,7 +695,8 @@ export class DocumentView extends DocComponent(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("(Docu color: foregroundColor, outlineColor: "maroon", outlineStyle: "dashed", - boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`, + boxShadow: this.layoutDoc.isBackground ? + `0px 0px 50px 50px ${groupCol}` : + `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`, outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? @@ -714,7 +717,7 @@ export class DocumentView extends DocComponent(Docu border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined, borderRadius: "inherit", - background: backgroundColor, + background: this.layoutDoc.isBackground ? groupCol : backgroundColor, width: nativeWidth, height: nativeHeight, transform: `scale(${this.props.ContentScaling()})`, -- cgit v1.2.3-70-g09d2 From 982961c71fdd9d5dcd02ea33a2b631076a6a1f4b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 13:31:12 -0400 Subject: refactor --- src/client/util/DictationManager.ts | 307 +++++++++++---------- src/client/views/GlobalKeyHandler.ts | 14 +- .../views/collections/CollectionBaseView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- 4 files changed, 165 insertions(+), 162 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b0866a826..b51d309a1 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,183 +11,190 @@ import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; -namespace CORE { - export interface IWindow extends Window { - webkitSpeechRecognition: any; - } -} - -const ConstructorMap = new Map([ - [DocumentType.COL, listSpec(Doc)], - [DocumentType.AUDIO, AudioField], - [DocumentType.IMG, ImageField], - [DocumentType.HIST, HistogramField], - [DocumentType.IMPORT, listSpec(Doc)] -]); - -const tryCast = (view: DocumentView, type: DocumentType) => { - let ctor = ConstructorMap.get(type); - if (!ctor) { - return false; - } - return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; -}; +export namespace DictationManager { -const validate = (target: DocumentView, types: DocumentType[]) => { - for (let type of types) { - if (tryCast(target, type)) { - return true; + namespace CORE { + export interface IWindow extends Window { + webkitSpeechRecognition: any; } } - return false; -}; - -const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; -export type IndependentAction = (target: DocumentView) => any | Promise; -export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegistrationEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; -export type ActionPredicate = (target: DocumentView) => boolean; -export type RegexEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; - -export default class DictationManager { - public static Instance = new DictationManager(); - private recognizer: any; - private isListening = false; - - constructor() { - this.recognizer = new webkitSpeechRecognition(); - this.recognizer.interimResults = false; - this.recognizer.continuous = true; - } - finish = (handler: any, data: any) => { - handler(data); - this.stop(); - } + const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; - stop = () => { - this.recognizer.stop(); - this.isListening = false; - } + let isListening = false; + const recognizer = (() => { + let initialized = new webkitSpeechRecognition(); + initialized.interimResults = false; + initialized.continuous = true; + return initialized; + })(); - listen = () => { - if (this.isListening) { - return undefined; - } - this.isListening = true; - this.recognizer.start(); - return new Promise((resolve, reject) => { - this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript); - this.recognizer.onerror = (e: any) => this.finish(reject, e); - }); - } + export namespace Controls { + + export const listen = () => { + if (isListening) { + return undefined; + } + isListening = true; + recognizer.start(); + return new Promise((resolve, reject) => { + recognizer.onresult = (e: any) => { + resolve(e.results[0][0].transcript); + stop(); + }; + recognizer.onerror = (e: any) => { + reject(e); + stop(); + }; + }); + }; + + export const stop = () => { + recognizer.stop(); + isListening = false; + }; - public interpretNumber = (number: string) => { - let initial = parseInt(number); - if (!isNaN(initial)) { - return initial; - } - let converted = converter.wordsToNumbers(number, { fuzzy: true }); - if (converted === null) { - return NaN; - } - return typeof converted === "string" ? parseInt(converted) : converted; } - @undoBatch - public execute = async (phrase: string) => { - let targets = SelectionManager.SelectedDocuments(); - if (!targets || !targets.length) { - return; - } + export namespace Commands { - let entry = RegisteredCommands.Independent.get(phrase); - if (entry) { - let success = false; - for (let target of targets) { - if (!entry.restrictTo || validate(target, entry.restrictTo)) { - await entry.action(target); - success = true; - } + export type IndependentAction = (target: DocumentView) => any | Promise; + export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; + + export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; + export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; + + export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); + export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); + + export const execute = async (phrase: string) => { + let targets = SelectionManager.SelectedDocuments(); + if (!targets || !targets.length) { + return; } - return success; - } - for (let entry of RegisteredCommands.Dependent) { - let regex = entry.expression; - let matches = regex.exec(phrase); - regex.lastIndex = 0; - if (matches !== null) { + let entry = Independent.get(phrase); + if (entry) { let success = false; + let restrictTo = entry.restrictTo; for (let target of targets) { - if (!entry.restrictTo || validate(target, entry.restrictTo)) { - await entry.action(target, matches); + if (!restrictTo || validate(target, restrictTo)) { + await entry.action(target); success = true; } } return success; } - } - return false; - } + for (let entry of Dependent) { + let regex = entry.expression; + let matches = regex.exec(phrase); + regex.lastIndex = 0; + if (matches !== null) { + let success = false; + let restrictTo = entry.restrictTo; + for (let target of targets) { + if (!restrictTo || validate(target, restrictTo)) { + await entry.action(target, matches); + success = true; + } + } + return success; + } + } -} + return false; + }; + + const ConstructorMap = new Map([ + [DocumentType.COL, listSpec(Doc)], + [DocumentType.AUDIO, AudioField], + [DocumentType.IMG, ImageField], + [DocumentType.HIST, HistogramField], + [DocumentType.IMPORT, listSpec(Doc)] + ]); + + const tryCast = (view: DocumentView, type: DocumentType) => { + let ctor = ConstructorMap.get(type); + if (!ctor) { + return false; + } + return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; + }; -export namespace RegisteredCommands { + const validate = (target: DocumentView, types: DocumentType[]) => { + for (let type of types) { + if (tryCast(target, type)) { + return true; + } + } + return false; + }; - export const Independent = new Map([ + const interpretNumber = (number: string) => { + let initial = parseInt(number); + if (!isNaN(initial)) { + return initial; + } + let converted = converter.wordsToNumbers(number, { fuzzy: true }); + if (converted === null) { + return NaN; + } + return typeof converted === "string" ? parseInt(converted) : converted; + }; - ["clear", { - action: (target: DocumentView) => { - Doc.GetProto(target.props.Document).data = new List(); - }, - restrictTo: [DocumentType.COL] - }], + const Independent = new Map([ - ["open fields", { - action: (target: DocumentView) => { - let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); - } - }] - - ]); - - export const Dependent = new Array( - - { - expression: /create (\w+) documents of type (image|nested collection)/g, - action: (target: DocumentView, matches: RegExpExecArray) => { - let count = DictationManager.Instance.interpretNumber(matches[1]); - let what = matches[2]; - let dataDoc = Doc.GetProto(target.props.Document); - let fieldKey = "data"; - for (let i = 0; i < count; i++) { - let created: Doc | undefined; - switch (what) { - case "image": - created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); - break; - case "nested collection": - created = Docs.Create.FreeformDocument([], {}); - break; - } - created && Doc.AddDocToList(dataDoc, fieldKey, created); + ["clear", { + action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(), + restrictTo: [DocumentType.COL] + }], + + ["open fields", { + action: (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); } + }] + + ]); + + const Dependent = new Array( + + { + expression: /create (\w+) documents of type (image|nested collection)/g, + action: (target: DocumentView, matches: RegExpExecArray) => { + let count = interpretNumber(matches[1]); + let what = matches[2]; + let dataDoc = Doc.GetProto(target.props.Document); + let fieldKey = "data"; + for (let i = 0; i < count; i++) { + let created: Doc | undefined; + switch (what) { + case "image": + created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + break; + case "nested collection": + created = Docs.Create.FreeformDocument([], {}); + break; + } + created && Doc.AddDocToList(dataDoc, fieldKey, created); + } + }, + restrictTo: [DocumentType.COL] }, - restrictTo: [DocumentType.COL] - }, - - { - expression: /view as (freeform|stacking|masonry|schema|tree)/g, - action: (target: DocumentView, matches: RegExpExecArray) => { - let mode = CollectionViewType.ValueOf(matches[1]); - mode && (target.props.Document.viewType = mode); - }, - restrictTo: [DocumentType.COL] - } - ); + { + expression: /view as (freeform|stacking|masonry|schema|tree)/g, + action: (target: DocumentView, matches: RegExpExecArray) => { + let mode = CollectionViewType.valueOf(matches[1]); + mode && (target.props.Document.viewType = mode); + }, + restrictTo: [DocumentType.COL] + } + + ); + + } } \ No newline at end of file diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 99345f04e..609136bb5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -5,10 +5,7 @@ import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; import { action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; -import { CognitiveServices } from "../cognitive_services/CognitiveServices"; -import DictationManager from "../util/DictationManager"; -import { ContextMenu } from "./ContextMenu"; -import { ContextMenuProps } from "./ContextMenuItem"; +import { DictationManager } from "../util/DictationManager"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -74,7 +71,7 @@ export default class KeyManager { } main.toggleColorPicker(true); SelectionManager.DeselectAll(); - DictationManager.Instance.stop(); + DictationManager.Controls.stop(); main.dictationOverlayVisible = false; main.dictationSuccess = undefined; main.overlayTimeout && clearTimeout(main.overlayTimeout); @@ -114,19 +111,18 @@ export default class KeyManager { let main = MainView.Instance; main.dictationOverlayVisible = true; main.isListening = true; - let dictation = DictationManager.Instance; - let command = await dictation.listen(); + let command = await DictationManager.Controls.listen(); main.isListening = false; if (!command) { break; } command = command.toLowerCase(); main.dictatedPhrase = command; - main.dictationSuccess = await dictation.execute(command); + main.dictationSuccess = await DictationManager.Commands.execute(command); main.overlayTimeout = setTimeout(() => { main.dictationOverlayVisible = false; main.dictationSuccess = undefined; - }, 3000); + }, 2000); stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 3f88ed98c..24604c812 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -34,7 +34,7 @@ export namespace CollectionViewType { ["masonry", CollectionViewType.Masonry] ]); - export const ValueOf = (value: string) => { + export const valueOf = (value: string) => { return stringMapping.get(value.toLowerCase()); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 51661d1ae..c5d526a5a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,7 +41,7 @@ import { ClientUtils } from '../../util/ClientUtils'; import { EditableView } from '../EditableView'; import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons'; import { DocumentDecorations } from '../DocumentDecorations'; -import DictationManager from '../../util/DictationManager'; +import { DictationManager } from '../../util/DictationManager'; import { CollectionViewType } from '../collections/CollectionBaseView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -539,7 +539,7 @@ export class DocumentView extends DocComponent(Docu } listen = async () => { - let transcript = await DictationManager.Instance.listen(); + let transcript = await DictationManager.Controls.listen(); transcript && (Doc.GetProto(this.props.Document).transcript = transcript); } -- cgit v1.2.3-70-g09d2 From 30444bd06dbd00c948749fa739e7a42cef87ec85 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 14:05:33 -0400 Subject: added final or interim listning --- src/client/util/DictationManager.ts | 35 +++++++++++++++++++++++++++-------- src/client/views/GlobalKeyHandler.ts | 2 +- 2 files changed, 28 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b51d309a1..2ed74a729 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -2,7 +2,7 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; import { undoBatch } from "./UndoManager"; import * as converter from "words-to-numbers"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs, DocumentType } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; @@ -18,40 +18,59 @@ export namespace DictationManager { webkitSpeechRecognition: any; } } - const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; let isListening = false; const recognizer = (() => { let initialized = new webkitSpeechRecognition(); - initialized.interimResults = false; initialized.continuous = true; return initialized; })(); export namespace Controls { - export const listen = () => { + export type InterimResultHandler = (results: any) => any; + + export const listen = (handler: Opt = undefined) => { if (isListening) { return undefined; } isListening = true; + + recognizer.interimResults = handler !== undefined; recognizer.start(); + return new Promise((resolve, reject) => { - recognizer.onresult = (e: any) => { - resolve(e.results[0][0].transcript); - stop(); - }; recognizer.onerror = (e: any) => { reject(e); stop(); }; + if (handler) { + let newestResult: string; + recognizer.onresult = (e: any) => { + newestResult = e.results[0][0].transcript; + handler(newestResult); + }; + recognizer.onend = (e: any) => { + resolve(newestResult); + stop(); + }; + } else { + recognizer.onresult = (e: any) => { + let finalResult = e.results[0][0].transcript; + resolve(finalResult); + stop(); + }; + } }); }; export const stop = () => { recognizer.stop(); isListening = false; + recognizer.onresult = undefined; + recognizer.onend = undefined; + recognizer.onerror = undefined; }; } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 609136bb5..b1ea92bb8 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -111,7 +111,7 @@ export default class KeyManager { let main = MainView.Instance; main.dictationOverlayVisible = true; main.isListening = true; - let command = await DictationManager.Controls.listen(); + let command = await DictationManager.Controls.listen((results: any) => console.log(results)); main.isListening = false; if (!command) { break; -- cgit v1.2.3-70-g09d2 From 753615c80d4cf08605ebaaeeaf0a44a0fd88d898 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 3 Aug 2019 15:21:10 -0400 Subject: working version of clustering --- src/Utils.ts | 2 + src/client/documents/Documents.ts | 3 +- src/client/util/DragManager.ts | 8 +- src/client/views/MainView.tsx | 4 +- src/client/views/TemplateMenu.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 3 +- .../views/collections/CollectionSchemaView.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 142 +++++++++++++-------- .../collections/collectionFreeForm/MarqueeView.tsx | 1 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 12 +- src/client/views/nodes/DocumentView.tsx | 17 +-- .../views/presentationview/PresentationElement.tsx | 3 +- src/client/views/search/SearchItem.tsx | 3 +- 13 files changed, 125 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 8df67df5d..502540eb0 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -140,6 +140,8 @@ export function returnOne() { return 1; } export function returnZero() { return 0; } +export function returnEmptyString() { return ""; } + export function emptyFunction() { } export type Without = Pick>; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 09bafcf43..07e38a4c0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -84,6 +84,7 @@ export interface DocumentOptions { templates?: List; viewType?: number; backgroundColor?: string; + defaultBackgroundColor?: string; dropAction?: dropActionType; backgroundLayout?: string; chromeStatus?: string; @@ -124,7 +125,7 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.TEXT, { layout: { view: FormattedTextBox }, - options: { height: 150, backgroundColor: "#f1efeb" } + options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" } }], [DocumentType.HIST, { layout: { view: HistogramBox, collectionView: [CollectionView, data] as CollectionViewType }, diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0b5c785a4..a7aaaed7c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -404,7 +404,8 @@ export namespace DragManager { hideSource = options.hideSource(); } } - eles.map(ele => (ele.hidden = hideSource)); + eles.map(ele => (ele.hidden = hideSource) && + (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = hideSource))); let lastX = downX; let lastY = downY; @@ -434,7 +435,10 @@ export namespace DragManager { let hideDragElements = () => { dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); - eles.map(ele => (ele.hidden = false)); + eles.map(ele => { + ele.hidden = false; + (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = false)); + }); }; let endDrag = () => { document.removeEventListener("pointermove", moveHandler, true); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2ecf5fd85..53f589684 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema'; import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; -import { emptyFunction, returnOne, returnTrue, Utils } from '../../Utils'; +import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils'; import { DocServer } from '../DocServer'; import { Docs } from '../documents/Documents'; import { SetupDrag } from '../util/DragManager'; @@ -270,6 +270,7 @@ export class MainView extends React.Component { PanelWidth={this.getPWidth} PanelHeight={this.getPHeight} renderDepth={0} + backgroundColor={returnEmptyString} selectOnLoad={false} focus={emptyFunction} parentActive={returnTrue} @@ -334,6 +335,7 @@ export class MainView extends React.Component { renderDepth={0} selectOnLoad={false} focus={emptyFunction} + backgroundColor={returnEmptyString} parentActive={returnTrue} whenActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index e654a0644..6dd908445 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -60,12 +60,9 @@ export class TemplateMenu extends React.Component { let de = new DragManager.DocumentDragData([topDoc], [undefined]); de.moveDocument = topDocView.props.moveDocument; let xf = newDocView.ContentDiv!.getBoundingClientRect(); - console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left)); DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, { offsetX: (ex - xf.left), offsetY: (ey - xf.top), - handlers: { - dragComplete: () => { }, - }, + handlers: { dragComplete: () => { }, }, hideSource: false }); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 588102f01..f559480ed 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -10,7 +10,7 @@ import { Id } from '../../../new_fields/FieldSymbols'; import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { emptyFunction, returnTrue, Utils, returnOne } from "../../../Utils"; +import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DocumentManager } from '../../util/DocumentManager'; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; @@ -607,6 +607,7 @@ export class DockedFrameRenderer extends React.Component { parentActive={returnTrue} whenActiveChanged={emptyFunction} focus={emptyFunction} + backgroundColor={returnEmptyString} addDocTab={this.addDocTab} ContainingCollectionView={undefined} zoomToScale={emptyFunction} diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 9efd0d3ec..8218877ba 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -6,7 +6,7 @@ import { action, computed, observable, trace, untracked } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize } from "react-table"; import "react-table/react-table.css"; -import { emptyFunction, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, returnEmptyString } from "../../../Utils"; import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; @@ -999,6 +999,7 @@ export class CollectionSchemaPreview extends React.Component r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); } - _groupingBorder = 100; + _clusterDistance = 75; bounsdSelect(doc: Doc, doc2: Doc) { - var x2 = NumCast(doc2.x) - this._groupingBorder; - var y2 = NumCast(doc2.y) - this._groupingBorder; - var w2 = NumCast(doc2.width) + this._groupingBorder; - var h2 = NumCast(doc2.height) + this._groupingBorder; - var x = NumCast(doc.x) - this._groupingBorder; - var y = NumCast(doc.y) - this._groupingBorder; - var w = NumCast(doc.width) + this._groupingBorder; - var h = NumCast(doc.height) + this._groupingBorder; - if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) { + var x2 = NumCast(doc2.x) - this._clusterDistance; + var y2 = NumCast(doc2.y) - this._clusterDistance; + var w2 = NumCast(doc2.width) + this._clusterDistance; + var h2 = NumCast(doc2.height) + this._clusterDistance; + var x = NumCast(doc.x) - this._clusterDistance; + var y = NumCast(doc.y) - this._clusterDistance; + var w = NumCast(doc.width) + this._clusterDistance; + var h = NumCast(doc.height) + this._clusterDistance; + if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) { return true; } return false; @@ -197,36 +199,83 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } + tryDragCluster(e: PointerEvent) { + let probe = this.getTransform().transformPoint(e.clientX, e.clientY); + let cluster = this.childDocs.reduce((cluster, cd) => { + let cx = NumCast(cd.x) - this._clusterDistance; + let cy = NumCast(cd.y) - this._clusterDistance; + let cw = NumCast(cd.width) + 2 * this._clusterDistance; + let ch = NumCast(cd.height) + 2 * this._clusterDistance; + if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) + return NumCast(cd.cluster); + return cluster; + }, -1); + if (cluster !== -1) { + let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + this.selectDocuments(eles); + let clusterDocs = SelectionManager.SelectedDocuments(); + SelectionManager.DeselectAll(); + let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); + de.moveDocument = this.props.moveDocument; + const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); + const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); + de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; + de.xOffset = xoff; + de.yOffset = yoff; + DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { + handlers: { dragComplete: action(emptyFunction) }, + hideSource: !de.dropAction + }); + return true; + } + + return false; + } + @observable sets: (Doc[])[] = []; @action updateClusters() { - let sets: (Doc[])[] = [] + this.sets.length = 0; this.childDocs.map(c => { let included = [] - for (let i = 0; i < sets.length; i++) { - for (let j = 0; j < sets[i].length; j++) { - if (this.bounsdSelect(c, sets[i][j])) { + for (let i = 0; i < this.sets.length; i++) { + for (let j = 0; j < this.sets[i].length; j++) { + if (this.bounsdSelect(c, this.sets[i][j])) { included.push(i); break; } } } if (included.length === 0) - sets.push([c]); + this.sets.push([c]); else if (included.length === 1) - sets[included[0]].push(c); + this.sets[included[0]].push(c); else { - sets[included[0]].push(c); + this.sets[included[0]].push(c); for (let s = 1; s < included.length; s++) { - sets[included[0]].push(...sets[included[s]]); - sets[included[s]].length = 0; + this.sets[included[0]].push(...this.sets[included[s]]); + this.sets[included[s]].length = 0; } } }); - for (let s = 0; s < sets.length; s++) { - for (let i = 0; i < sets[s].length; i++) { - Doc.GetProto(sets[s][i]).cluster = s; + for (let s = 0; s < this.sets.length; s++) { + for (let i = 0; i < this.sets[s].length; i++) { + this.sets[s][i].cluster = s; + } + } + } + + getClusterColor = (doc: Doc) => { + if (this.props.Document.useClusters) { + let cluster = NumCast(doc.cluster); + let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined; + let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; + let clusterColor = colors[cluster % colors.length]; + for (let i = 0; set && i < set.length; i++) { + if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor); } + return clusterColor; } + return ""; } @action @@ -249,34 +298,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble) { - let probe = this.getTransform().transformPoint(e.clientX, e.clientY); - let cluster = this.childDocs.reduce((cluster, cd) => { - let cx = NumCast(cd.x) - this._groupingBorder; - let cy = NumCast(cd.y) - this._groupingBorder; - let cw = NumCast(cd.width) + 2 * this._groupingBorder; - let ch = NumCast(cd.height) + 2 * this._groupingBorder; - if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) - return NumCast(cd.cluster); - return cluster; - }, -1); - if (cluster !== -1) { - let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); - this.selectDocuments(eles); - let clusterDocs = SelectionManager.SelectedDocuments(); - SelectionManager.DeselectAll(); - let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); - de.moveDocument = this.props.moveDocument; - const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); - const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); - de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; - de.xOffset = xoff; - de.yOffset = yoff; - DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { - handlers: { - dragComplete: action(emptyFunction) - }, - hideSource: !de.dropAction - }); + if (this.props.Document.useClusters && this.tryDragCluster(e)) { e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); document.removeEventListener("pointermove", this.onPointerMove); @@ -493,6 +515,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, + backgroundColor: this.getClusterColor, parentActive: this.props.active, whenActiveChanged: this.props.whenActiveChanged, bringToFront: this.bringToFront, @@ -516,6 +539,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, + backgroundColor: returnEmptyString, parentActive: this.props.active, whenActiveChanged: this.props.whenActiveChanged, bringToFront: this.bringToFront, @@ -625,6 +649,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { event: async () => this.props.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ + description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, + event: async () => { + Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; + Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; + this.props.Document.useClusters = !this.props.Document.useClusters; + }, + icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt" + }); layoutItems.push({ description: "Arrange contents in grid", icon: "table", @@ -700,10 +733,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ...this.views ] private overlayChildViews = () => { - console.log(this.overlayViews.length); - return [ - ...this.overlayViews - ]; + return [...this.overlayViews]; } public static AddCustomLayout(doc: Doc, dataKey: string): () => void { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b9ee588dd..ff96bd993 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -278,6 +278,7 @@ export class MarqueeView extends React.Component panX: 0, panY: 0, backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", + defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", width: bounds.width, height: bounds.height, title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 3b6c443c2..ee596c841 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -8,6 +8,7 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView" import "./DocumentView.scss"; import React = require("react"); import { Doc } from "../../../new_fields/Doc"; +import { returnEmptyString } from "../../../Utils"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { x?: number; @@ -69,6 +70,11 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; + render() { const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return ( @@ -77,7 +83,10 @@ export class CollectionFreeFormDocumentView extends DocComponent void; collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; zoomToScale: (scale: number) => void; + backgroundColor: (doc: Doc) => string; getScale: () => number; animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; ChromeHeight?: () => number; @@ -675,12 +676,9 @@ export class DocumentView extends DocComponent(Docu // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } + render() { - if (this.Document.hidden) { - return null; - } - let self = this; - let backgroundColor = StrCast(this.layoutDoc.backgroundColor); + let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor); let foregroundColor = StrCast(this.layoutDoc.color); var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%"; var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; @@ -695,8 +693,6 @@ export class DocumentView extends DocComponent(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("(Docu color: foregroundColor, outlineColor: "maroon", outlineStyle: "dashed", - boxShadow: this.layoutDoc.isBackground ? - `0px 0px 50px 50px ${groupCol}` : - `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`, outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? @@ -717,7 +710,7 @@ export class DocumentView extends DocComponent(Docu border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ? `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined, borderRadius: "inherit", - background: this.layoutDoc.isBackground ? groupCol : backgroundColor, + background: backgroundColor, width: nativeWidth, height: nativeHeight, transform: `scale(${this.props.ContentScaling()})`, diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 11f3eb846..e2d8daea9 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -9,7 +9,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils, returnFalse, emptyFunction, returnOne } from "../../../Utils"; +import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils"; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; import { ContextMenu } from "../ContextMenu"; @@ -843,6 +843,7 @@ export default class PresentationElement extends React.Component 350} PanelHeight={() => 90} focus={emptyFunction} + backgroundColor={returnEmptyString} selectOnLoad={false} parentActive={returnFalse} whenActiveChanged={returnFalse} diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 0390359b3..1b9bba5c6 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils"; +import { emptyFunction, returnFalse, returnOne, Utils, returnEmptyString } from "../../../Utils"; import { DocumentType } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { SetupDrag, DragManager } from "../../util/DragManager"; @@ -223,6 +223,7 @@ export class SearchItem extends React.Component { PanelWidth={returnXDimension} PanelHeight={returnYDimension} focus={emptyFunction} + backgroundColor={returnEmptyString} selectOnLoad={false} parentActive={returnFalse} whenActiveChanged={returnFalse} -- cgit v1.2.3-70-g09d2 From 098e3780c9d16738070a92dbfcdf68372a9d18a9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 3 Aug 2019 15:35:51 -0400 Subject: from last --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ee21c5a4d..24439ef4c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -135,7 +135,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); } _clusterDistance = 75; - bounsdSelect(doc: Doc, doc2: Doc) { + boundsOverlap(doc: Doc, doc2: Doc) { var x2 = NumCast(doc2.x) - this._clusterDistance; var y2 = NumCast(doc2.y) - this._clusterDistance; var w2 = NumCast(doc2.width) + this._clusterDistance; @@ -239,7 +239,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let included = [] for (let i = 0; i < this.sets.length; i++) { for (let j = 0; j < this.sets[i].length; j++) { - if (this.bounsdSelect(c, this.sets[i][j])) { + if (this.boundsOverlap(c, this.sets[i][j])) { included.push(i); break; } -- cgit v1.2.3-70-g09d2 From 813476d753f0286d546a0b1c9f1b1774481604e0 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 17:17:43 -0400 Subject: final refactor and commenting --- src/client/util/DictationManager.ts | 29 +++++++++++++++++++++++++++-- src/client/views/GlobalKeyHandler.ts | 14 +++++++------- src/client/views/MainView.tsx | 16 ++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 2ed74a729..b799238ba 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,6 +1,6 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; -import { undoBatch } from "./UndoManager"; +import { UndoManager } from "./UndoManager"; import * as converter from "words-to-numbers"; import { Doc, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; @@ -11,8 +11,26 @@ import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; +/** + * This namespace provides a singleton instance of a manager that + * handles the listening and text-conversion of user speech. + * + * The basic manager functionality can be attained by the DictationManager.Controls namespace, which provide + * a simple recording operation that returns the interpreted text as a string. + * + * Additionally, however, the DictationManager also exposes the ability to execute voice commands within Dash. + * It stores a default library of registered commands that can be triggered by listen()'ing for a phrase and then + * passing the results into the execute() function. + * + * In addition to compile-time default commands, you can invoke DictationManager.Commands.Register(Independent|Dependent) + * to add new commands as classes or components are constructed. + */ export namespace DictationManager { + /** + * Some type maneuvering to access Webkit's built-in + * speech recognizer. + */ namespace CORE { export interface IWindow extends Window { webkitSpeechRecognition: any; @@ -24,6 +42,7 @@ export namespace DictationManager { const recognizer = (() => { let initialized = new webkitSpeechRecognition(); initialized.continuous = true; + initialized.language = "en-US"; return initialized; })(); @@ -77,6 +96,8 @@ export namespace DictationManager { export namespace Commands { + export const dictationFadeDuration = 2000; + export type IndependentAction = (target: DocumentView) => any | Promise; export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; @@ -91,14 +112,16 @@ export namespace DictationManager { if (!targets || !targets.length) { return; } - + phrase = phrase.toLowerCase(); let entry = Independent.get(phrase); if (entry) { let success = false; let restrictTo = entry.restrictTo; for (let target of targets) { if (!restrictTo || validate(target, restrictTo)) { + let batch = UndoManager.StartBatch("Independent Command"); await entry.action(target); + batch.end(); success = true; } } @@ -114,7 +137,9 @@ export namespace DictationManager { let restrictTo = entry.restrictTo; for (let target of targets) { if (!restrictTo || validate(target, restrictTo)) { + let batch = UndoManager.StartBatch("Dependent Command"); await entry.action(target, matches); + batch.end(); success = true; } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index b1ea92bb8..82289c249 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -74,7 +74,7 @@ export default class KeyManager { DictationManager.Controls.stop(); main.dictationOverlayVisible = false; main.dictationSuccess = undefined; - main.overlayTimeout && clearTimeout(main.overlayTimeout); + main.cancelDictationFade(); break; case "delete": case "backspace": @@ -110,19 +110,19 @@ export default class KeyManager { case " ": let main = MainView.Instance; main.dictationOverlayVisible = true; + main.isListening = true; let command = await DictationManager.Controls.listen((results: any) => console.log(results)); main.isListening = false; + if (!command) { break; } - command = command.toLowerCase(); - main.dictatedPhrase = command; + + main.dictatedPhrase = command = command.toLowerCase(); main.dictationSuccess = await DictationManager.Commands.execute(command); - main.overlayTimeout = setTimeout(() => { - main.dictationOverlayVisible = false; - main.dictationSuccess = undefined; - }, 2000); + main.initiateDictationFade(); + stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4a5e4a3d1..748e1e634 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -39,6 +39,7 @@ import { FilterBox } from './search/FilterBox'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { ClientUtils } from '../util/ClientUtils'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; +import { DictationManager } from '../util/DictationManager'; @observer export class MainView extends React.Component { @@ -55,6 +56,21 @@ export class MainView extends React.Component { public overlayTimeout: NodeJS.Timeout | undefined; + public initiateDictationFade = () => { + let duration = DictationManager.Commands.dictationFadeDuration; + this.overlayTimeout = setTimeout(() => { + this.dictationOverlayVisible = false; + this.dictationSuccess = undefined; + }, duration); + } + + public cancelDictationFade = () => { + if (this.overlayTimeout) { + clearTimeout(this.overlayTimeout); + this.overlayTimeout = undefined; + } + } + @computed private get mainContainer(): Opt { return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc)); } -- cgit v1.2.3-70-g09d2 From b7c70cf115ea3d23e06fc7a3837e983b62993334 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 3 Aug 2019 17:30:58 -0400 Subject: a few updates and an option to switch between default backgrounds and cluster backgrounds. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 20 +++++++++++++++----- src/client/views/nodes/DocumentView.tsx | 4 +++- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 24439ef4c..b4d065d26 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -267,12 +267,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getClusterColor = (doc: Doc) => { if (this.props.Document.useClusters) { let cluster = NumCast(doc.cluster); - let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined; + if (this.sets.length <= cluster) { + setTimeout(() => this.updateClusters(), 0); + return; + } + let set = this.sets.length > cluster ? this.sets[cluster] : undefined; let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; let clusterColor = colors[cluster % colors.length]; - for (let i = 0; set && i < set.length; i++) { - if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor); - } + set && set.filter(s => !s.isBackground).map(s => + s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); + set && set.filter(s => s.isBackground).map(s => + s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); return clusterColor; } return ""; @@ -652,12 +657,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { layoutItems.push({ description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: async () => { - Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; + Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; this.props.Document.useClusters = !this.props.Document.useClusters; }, icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ + description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, + event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, + icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt" + }); layoutItems.push({ description: "Arrange contents in grid", icon: "table", diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ae6a01ab4..0347fc9b2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -678,7 +678,9 @@ export class DocumentView extends DocComponent(Docu } render() { - let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor); + let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ? + this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : + StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); let foregroundColor = StrCast(this.layoutDoc.color); var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%"; var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; -- cgit v1.2.3-70-g09d2 From 6588455dfa091a1e925b4eeee5d018e23731b491 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 18:23:18 -0400 Subject: undoable, name change --- src/client/util/DictationManager.ts | 62 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b799238ba..b02a5ecbe 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,7 +1,7 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; -import { UndoManager } from "./UndoManager"; -import * as converter from "words-to-numbers"; +import { UndoManager, undoBatch } from "./UndoManager"; +import * as interpreter from "words-to-numbers"; import { Doc, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs, DocumentType } from "../documents/Documents"; @@ -108,46 +108,46 @@ export namespace DictationManager { export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); export const execute = async (phrase: string) => { - let targets = SelectionManager.SelectedDocuments(); - if (!targets || !targets.length) { - return; - } - phrase = phrase.toLowerCase(); - let entry = Independent.get(phrase); - if (entry) { - let success = false; - let restrictTo = entry.restrictTo; - for (let target of targets) { - if (!restrictTo || validate(target, restrictTo)) { - let batch = UndoManager.StartBatch("Independent Command"); - await entry.action(target); - batch.end(); - success = true; - } + return UndoManager.RunInBatch(async () => { + let targets = SelectionManager.SelectedDocuments(); + if (!targets || !targets.length) { + return; } - return success; - } - for (let entry of Dependent) { - let regex = entry.expression; - let matches = regex.exec(phrase); - regex.lastIndex = 0; - if (matches !== null) { + phrase = phrase.toLowerCase(); + let entry = Independent.get(phrase); + + if (entry) { let success = false; let restrictTo = entry.restrictTo; for (let target of targets) { if (!restrictTo || validate(target, restrictTo)) { - let batch = UndoManager.StartBatch("Dependent Command"); - await entry.action(target, matches); - batch.end(); + await entry.action(target); success = true; } } return success; } - } - return false; + for (let entry of Dependent) { + let regex = entry.expression; + let matches = regex.exec(phrase); + regex.lastIndex = 0; + if (matches !== null) { + let success = false; + let restrictTo = entry.restrictTo; + for (let target of targets) { + if (!restrictTo || validate(target, restrictTo)) { + await entry.action(target, matches); + success = true; + } + } + return success; + } + } + + return false; + }, "Execute Command"); }; const ConstructorMap = new Map([ @@ -180,7 +180,7 @@ export namespace DictationManager { if (!isNaN(initial)) { return initial; } - let converted = converter.wordsToNumbers(number, { fuzzy: true }); + let converted = interpreter.wordsToNumbers(number, { fuzzy: true }); if (converted === null) { return NaN; } -- cgit v1.2.3-70-g09d2 From e65a75245617e2161ad5a75dd2b91461144dfdf7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 3 Aug 2019 18:47:48 -0400 Subject: fixed marquee collection'ing with backgrounds. fixed rotating images. fixed marqueeing ink. --- src/client/views/InkingCanvas.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 21 +++++++++++--------- src/client/views/nodes/FormattedTextBox.tsx | 8 ++++---- src/client/views/nodes/ImageBox.tsx | 10 +++++----- src/new_fields/Doc.ts | 23 +++++++++++++--------- 5 files changed, 36 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index c4cd863d1..1c221e3df 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -176,7 +176,7 @@ export class InkingCanvas extends React.Component { } render() { - let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect"; + let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect"; return (
    diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ff96bd993..aad26efa0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -226,15 +226,17 @@ export class MarqueeView extends React.Component } get ink() { - let container = this.props.container.Document; + let container = this.props.container.props.Document; let containerKey = this.props.container.props.fieldKey; - return Cast(container[containerKey + "_ink"], InkField); + let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); + return Cast(extensionDoc.ink, InkField); } set ink(value: InkField | undefined) { - let container = Doc.GetProto(this.props.container.Document); + let container = Doc.GetProto(this.props.container.props.Document); let containerKey = this.props.container.props.fieldKey; - container[containerKey + "_ink"] = value; + let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); + extensionDoc.ink = value; } @undoBatch @@ -247,7 +249,7 @@ export class MarqueeView extends React.Component this._commandExecuted = true; e.stopPropagation(); (e as any).propagationIsStopped = true; - this.marqueeSelect().map(d => this.props.removeDocument(d)); + this.marqueeSelect(false).map(d => this.props.removeDocument(d)); if (this.ink) { this.marqueeInkDelete(this.ink.inkData); } @@ -261,7 +263,7 @@ export class MarqueeView extends React.Component e.preventDefault(); (e as any).propagationIsStopped = true; let bounds = this.Bounds; - let selected = this.marqueeSelect(); + let selected = this.marqueeSelect(false); if (e.key === "c") { selected.map(d => { this.props.removeDocument(d); @@ -283,7 +285,8 @@ export class MarqueeView extends React.Component height: bounds.height, title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", }); - newCollection.data_ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; + let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); + dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); if (e.key === "s") { @@ -367,7 +370,7 @@ export class MarqueeView extends React.Component } } - marqueeSelect() { + marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => { @@ -379,7 +382,7 @@ export class MarqueeView extends React.Component selection.push(doc); } }); - if (!selection.length) { + if (!selection.length && selectBackgrounds) { this.props.activeDocuments().map(doc => { var x = NumCast(doc.x); var y = NumCast(doc.y); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index e076efe18..6c97add76 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -674,7 +674,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let self = this; let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; - let interactive = InkingControl.Instance.selectedTool ? "" : "interactive"; + let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground || + (this.props.Document.isButton && !this.props.isSelected()) ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return (
    -
    +
    ); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 17dc4184a..0d9c2bb8a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -314,14 +314,14 @@ export class ImageBox extends DocComponent(ImageD resize(srcpath: string, layoutdoc: Doc) { requestImageSize(srcpath) .then((size: any) => { - let aspect = size.height / size.width; let rotation = NumCast(this.dataDoc.rotation) % 180; - if (rotation === 90 || rotation === 270) aspect = 1 / aspect; - if (Math.abs(NumCast(layoutdoc.height) - size.height) > 1 || Math.abs(NumCast(layoutdoc.width) - size.width) > 1) { + let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; + let aspect = realsize.height / realsize.width; + if (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; - layoutdoc.nativeHeight = size.height; - layoutdoc.nativeWidth = size.width; + layoutdoc.nativeHeight = realsize.height; + layoutdoc.nativeWidth = realsize.width; }), 0); } }) diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index b70951040..b081588a3 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -341,19 +341,24 @@ export namespace Doc { return fieldExt && doc[fieldKey + "_ext"] instanceof Doc ? doc[fieldKey + "_ext"] as Doc : doc; } + export function CreateDocumentExtensionForField(doc: Doc, fieldKey: string) { + let docExtensionForField = new Doc(doc[Id] + fieldKey, true); + docExtensionForField.title = fieldKey + ".ext"; + docExtensionForField.extendsDoc = doc; // this is used by search to map field matches on the extension doc back to the document it extends. + docExtensionForField.type = DocumentType.EXTENSION; + let proto: Doc | undefined = doc; + while (proto && !Doc.IsPrototype(proto)) { + proto = proto.proto; + } + (proto ? proto : doc)[fieldKey + "_ext"] = new PrefetchProxy(docExtensionForField); + return docExtensionForField; + } + export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string) { let docExtensionForField = doc[fieldKey + "_ext"] as Doc; if (docExtensionForField === undefined) { setTimeout(() => { - docExtensionForField = new Doc(doc[Id] + fieldKey, true); - docExtensionForField.title = fieldKey + ".ext"; - docExtensionForField.extendsDoc = doc; // this is used by search to map field matches on the extension doc back to the document it extends. - docExtensionForField.type = DocumentType.EXTENSION; - let proto: Doc | undefined = doc; - while (proto && !Doc.IsPrototype(proto)) { - proto = proto.proto; - } - (proto ? proto : doc)[fieldKey + "_ext"] = new PrefetchProxy(docExtensionForField); + CreateDocumentExtensionForField(doc, field); }, 0); } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0); -- cgit v1.2.3-70-g09d2 From d6fda11588f1a117e8acc30ea5600d34ff22e01b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 21:29:56 -0400 Subject: now differentiate between continuous, indefinite and, separately, interim vs final only dictation results --- src/client/util/DictationManager.ts | 81 +++++++++++++++++++++------------ src/client/views/GlobalKeyHandler.ts | 3 +- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +- src/new_fields/Doc.ts | 2 +- 5 files changed, 59 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b02a5ecbe..89797f101 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -39,57 +39,82 @@ export namespace DictationManager { const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; let isListening = false; - const recognizer = (() => { - let initialized = new webkitSpeechRecognition(); - initialized.continuous = true; - initialized.language = "en-US"; - return initialized; - })(); + let isManuallyStopped = false; + const recognizer: SpeechRecognition = new webkitSpeechRecognition(); export namespace Controls { + let newestResult: string; export type InterimResultHandler = (results: any) => any; + export type ContinuityArgs = { indefinite: boolean } | false; + export interface ListeningOptions { + language: string; + continuous: ContinuityArgs; + interimHandler: InterimResultHandler; + delimiter: string; + } - export const listen = (handler: Opt = undefined) => { + export const listen = (options?: Partial) => { if (isListening) { return undefined; } isListening = true; + let handler = options ? options.interimHandler : undefined; + let continuous = options ? options.continuous : undefined; + let language = options ? options.language : undefined; + let delimiter = options ? options.delimiter : undefined; + recognizer.interimResults = handler !== undefined; + recognizer.continuous = continuous === undefined ? false : continuous !== false; + recognizer.lang = language === undefined ? "en-US" : language; + recognizer.start(); return new Promise((resolve, reject) => { + recognizer.onerror = (e: any) => { reject(e); stop(); }; - if (handler) { - let newestResult: string; - recognizer.onresult = (e: any) => { - newestResult = e.results[0][0].transcript; - handler(newestResult); - }; - recognizer.onend = (e: any) => { + + recognizer.onresult = (e: SpeechRecognitionEvent) => { + newestResult = synthesize(e, delimiter); + handler && handler(newestResult); + }; + + recognizer.onend = (e: Event) => { + if (continuous && continuous.indefinite && !isManuallyStopped) { + recognizer.start(); + } else { resolve(newestResult); - stop(); - }; - } else { - recognizer.onresult = (e: any) => { - let finalResult = e.results[0][0].transcript; - resolve(finalResult); - stop(); - }; - } + reset(); + } + }; + }); }; - export const stop = () => { - recognizer.stop(); + export const stop = (saveCumulative = true) => { + saveCumulative ? recognizer.stop() : recognizer.abort(); + reset(); + }; + + const reset = () => { isListening = false; - recognizer.onresult = undefined; - recognizer.onend = undefined; - recognizer.onerror = undefined; + isManuallyStopped = false; + recognizer.onresult = null; + recognizer.onend = null; + recognizer.onerror = null; + }; + + const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { + let results = e.results; + let transcripts: string[] = []; + for (let i = 0; i < results.length; i++) { + transcripts.push(results.item(i).item(0).transcript.trim()); + } + return transcripts.join(delimiter || "..."); }; } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 82289c249..98df43a1e 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -112,7 +112,8 @@ export default class KeyManager { main.dictationOverlayVisible = true; main.isListening = true; - let command = await DictationManager.Controls.listen((results: any) => console.log(results)); + // let printer = (results: any) => console.log(results); + let command = await DictationManager.Controls.listen(); main.isListening = false; if (!command) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 748e1e634..5cec34293 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,7 +49,7 @@ export class MainView extends React.Component { @observable public pwidth: number = 0; @observable public pheight: number = 0; - @observable private dictationState = "Listening..."; + @observable private dictationState = ""; @observable private dictationSuccessState: boolean | undefined = undefined; @observable private dictationDisplayState = false; @observable private dictationListeningState = false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c5d526a5a..bd87bf21b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -539,7 +539,8 @@ export class DocumentView extends DocComponent(Docu } listen = async () => { - let transcript = await DictationManager.Controls.listen(); + let options = { continuous: { indefinite: true }, delimiter: " " }; + let transcript = await DictationManager.Controls.listen(options); transcript && (Doc.GetProto(this.props.Document).transcript = transcript); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 84b8589dd..979574f16 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -531,7 +531,7 @@ export namespace Doc { d.layout = detailLayout; d.nativeWidth = d.nativeHeight = undefined; if (detailLayout instanceof Doc) { - let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc; + let delegDetailLayout = Doc.MakeDelegate(detailLayout); d.layout = delegDetailLayout; let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout); let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout); -- cgit v1.2.3-70-g09d2 From 133d890f74e6ad927dda366015fd3a4ddb7497c8 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 21:37:53 -0400 Subject: improved error handling --- src/client/util/DictationManager.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 89797f101..d022fea2e 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -54,7 +54,18 @@ export namespace DictationManager { delimiter: string; } - export const listen = (options?: Partial) => { + export const listen = async (options?: Partial) => { + let results: any; + try { + results = await listenImpl(options); + } catch (e) { + results = "Dication Error: "; + results += e instanceof SpeechRecognitionError ? e.error : "unknown error"; + } + return results; + }; + + const listenImpl = (options?: Partial) => { if (isListening) { return undefined; } -- cgit v1.2.3-70-g09d2 From 30e3964bd24ffcd3b0fa77b917a2a68bd34be6b5 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 21:39:44 -0400 Subject: clean up --- src/client/util/DictationManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index d022fea2e..fd5ee25a1 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,8 +1,8 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; -import { UndoManager, undoBatch } from "./UndoManager"; +import { UndoManager } from "./UndoManager"; import * as interpreter from "words-to-numbers"; -import { Doc, Opt } from "../../new_fields/Doc"; +import { Doc } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs, DocumentType } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; @@ -114,9 +114,9 @@ export namespace DictationManager { const reset = () => { isListening = false; isManuallyStopped = false; + recognizer.onerror = null; recognizer.onresult = null; recognizer.onend = null; - recognizer.onerror = null; }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { -- cgit v1.2.3-70-g09d2 From 056a03812f202c9588c25d543ffa316060b97b84 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 22:07:56 -0400 Subject: tweaks to indefinite transcription --- src/client/util/DictationManager.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index fd5ee25a1..fa34ca9ad 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -40,6 +40,7 @@ export namespace DictationManager { let isListening = false; let isManuallyStopped = false; + const results: string[] = []; const recognizer: SpeechRecognition = new webkitSpeechRecognition(); export namespace Controls { @@ -59,8 +60,8 @@ export namespace DictationManager { try { results = await listenImpl(options); } catch (e) { - results = "Dication Error: "; - results += e instanceof SpeechRecognitionError ? e.error : "unknown error"; + results = "Dictation Error: "; + results += "error" in e ? e.error : "unknown error"; } return results; }; @@ -91,14 +92,16 @@ export namespace DictationManager { recognizer.onresult = (e: SpeechRecognitionEvent) => { newestResult = synthesize(e, delimiter); + continuous && continuous.indefinite && results.push(newestResult); handler && handler(newestResult); }; recognizer.onend = (e: Event) => { - if (continuous && continuous.indefinite && !isManuallyStopped) { + let indefinite = continuous && continuous.indefinite; + if (indefinite && !isManuallyStopped) { recognizer.start(); } else { - resolve(newestResult); + resolve(indefinite ? newestResult : results.join(delimiter)); reset(); } }; -- cgit v1.2.3-70-g09d2 From bbca58e2ff087bbd4bb2ee12e6c4567e146450e3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 3 Aug 2019 23:47:26 -0400 Subject: fixed css for stacking views so that height/width are claculated correctly. --- .../views/collections/CollectionSchemaView.tsx | 6 +++-- .../views/collections/CollectionStackingView.scss | 4 ++++ .../views/collections/CollectionStackingView.tsx | 26 ++++++++++------------ 3 files changed, 20 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 8218877ba..2d4c88b94 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -974,14 +974,16 @@ export class CollectionSchemaPreview extends React.Component
    ; - return (
    + return (
    {!this.props.Document || !this.props.width ? (null) : (
    doc) { return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } + @computed + get SectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : "" } + get Sections() { - let sectionFilter = StrCast(this.props.Document.sectionFilter); let sectionHeaders = this.sectionHeaders; if (!sectionHeaders) { this.props.Document.sectionHeaders = sectionHeaders = new List(); } let fields = new Map(sectionHeaders.map(sh => [sh, []])); - if (sectionFilter) { + if (this.SectionFilter) { this.filteredChildren.map(d => { - let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `NO ${sectionFilter.toUpperCase()} VALUE`) as object; + let sectionValue = (d[this.SectionFilter] ? d[this.SectionFilter] : `NO ${this.SectionFilter.toUpperCase()} VALUE`) as object; // the next five lines ensures that floating point rounding errors don't create more than one section -syip let parsed = parseInt(sectionValue.toString()); let castedSectionValue: any = sectionValue; @@ -64,17 +66,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } // look for if header exists already - let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`)); + let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`)); if (existingHeader) { fields.get(existingHeader)!.push(d); } else { - let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`); + let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`); fields.set(newSchemaHeader, [d]); sectionHeaders!.push(newSchemaHeader); } }); - } + } else fields.clear(); return fields; } @@ -94,7 +96,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { // reset section headers when a new filter is inputted this._sectionFilterDisposer = reaction( - () => StrCast(this.props.Document.sectionFilter), + () => this.SectionFilter, () => { this.props.Document.sectionHeaders = new List(); } @@ -232,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }); } section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { - let key = StrCast(this.props.Document.sectionFilter); + let key = this.SectionFilter; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { @@ -294,14 +296,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return (
    e.stopPropagation()} > - {/* {sectionFilter as boolean ? [ - ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())], - ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)], - ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */} - {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). + {this.SectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). map(section => this.section(section[0], section[1])) : this.section(undefined, this.filteredChildren)} - {(this.props.Document.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ? + {(this.SectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
    -- cgit v1.2.3-70-g09d2 From 02346eabdefd428ca23d6a3fbefdcd51ef62b738 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 4 Aug 2019 01:17:29 -0400 Subject: fixed errors/warnings --- src/client/apis/youtube/YoutubeBox.tsx | 8 +- src/client/views/InkingControl.tsx | 4 +- src/client/views/TemplateMenu.tsx | 2 +- .../views/collections/CollectionSchemaCells.tsx | 4 +- .../views/collections/CollectionSchemaHeaders.tsx | 2 +- .../views/collections/CollectionSchemaView.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 116 +++++++++------------ src/client/views/collections/CollectionView.tsx | 11 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 23 ++-- src/client/views/nodes/ButtonBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 4 +- src/client/views/search/FilterBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 2 +- src/client/views/search/ToggleBar.tsx | 2 +- src/new_fields/Doc.ts | 4 +- 16 files changed, 82 insertions(+), 111 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index dc142802c..d73988bb8 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -57,7 +57,7 @@ export class YoutubeBox extends React.Component { if (awaitedBackUp) { - let jsonList = await DocListCastAsync(awaitedBackUp!.json); + let jsonList = await DocListCastAsync(awaitedBackUp.json); let jsonDetailList = await DocListCastAsync(awaitedDetails!.json); if (jsonList!.length !== 0) { @@ -76,8 +76,8 @@ export class YoutubeBox extends React.Component { let videoDescription = StrCast(snippet!.description); let pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!; let channelTitle = StrCast(snippet!.channelTitle); - let duration: string; - let viewCount: string; + let duration: string = ""; + let viewCount: string = ""; if (jsonDetailList!.length !== 0) { let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc); let statistics = await Cast(jsonDetailList![index].statistics, Doc); @@ -85,7 +85,7 @@ export class YoutubeBox extends React.Component { viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!; } index = index + 1; - let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! }; + let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration, viewCount: viewCount }; runInAction(() => this.curVideoTemplates.push(newTemplate)); } } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 58c83915b..3f40642b5 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -1,5 +1,5 @@ import { observable, action, computed, runInAction } from "mobx"; -import { ColorState } from 'react-color'; +import { ColorResult } from 'react-color'; import React = require("react"); import { observer } from "mobx-react"; import "./InkingControl.scss"; @@ -41,7 +41,7 @@ export class InkingControl extends React.Component { } @undoBatch - switchColor = action((color: ColorState): void => { + switchColor = action((color: ColorResult): void => { this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff"); if (InkingControl.Instance.selectedTool === InkTool.None) { if (MainOverlayTextBox.Instance.SetColor(color.hex)) return; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 6dd908445..393e97a7e 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -68,7 +68,7 @@ export class TemplateMenu extends React.Component { } }, 10); } else if (topDocView.props.ContainingCollectionView) { - let collView = topDocView.props.ContainingCollectionView!; + let collView = topDocView.props.ContainingCollectionView; let [sx, sy] = xf.inverse().transformPoint(0, 0); let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy); topDoc.x = x; diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 4ff65b277..7e3061354 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -175,11 +175,11 @@ export class CollectionSchemaCell extends React.Component { }; let onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) { - dragRef!.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; + dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; } }; let onPointerLeave = (e: React.PointerEvent): void => { - dragRef!.current!.className = "collectionSchemaView-cellContainer"; + dragRef.current!.className = "collectionSchemaView-cellContainer"; }; let contents: any = "incorrect type"; diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index dfd65770e..d24f63fbb 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -13,7 +13,7 @@ import { faFile } from "@fortawesome/free-regular-svg-icons"; import { SchemaHeaderField, RandomPastel, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; -library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile, faSortAmountDown, faSortAmountUp, faTimes); +library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes); export interface HeaderProps { keyValue: SchemaHeaderField; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 2d4c88b94..75787c0a8 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -303,14 +303,13 @@ export class SchemaTable extends React.Component { return resized; }, [] as { "id": string, "value": number }[]); } - - @computed get sorted(): { "id": string, "desc": boolean }[] { + @computed get sorted(): { "id": string, "desc"?: true }[] { return this.columns.reduce((sorted, shf) => { if (shf.desc) { sorted.push({ "id": shf.heading, "desc": shf.desc }); } return sorted; - }, [] as { "id": string, "desc": boolean }[]); + }, [] as { "id": string, "desc"?: true }[]); } @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d7c3ac3b8..4a751c84c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,26 +1,25 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, reaction, untracked, observable, runInAction } from "mobx"; +import { CursorProperty } from "csstype"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc"; +import Switch from 'rc-switch'; +import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; -import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, Utils, returnTrue } from "../../../Utils"; -import { CollectionSchemaPreview } from "./CollectionSchemaView"; -import "./CollectionStackingView.scss"; -import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; -import { undoBatch } from "../../util/UndoManager"; -import { DragManager } from "../../util/DragManager"; +import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction } from "../../../Utils"; import { DocumentType } from "../../documents/Documents"; +import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; -import { CursorProperty } from "csstype"; -import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; -import { listSpec } from "../../../new_fields/Schema"; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; -import { List } from "../../../new_fields/List"; +import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; -import { CollectionViewProps } from "./CollectionBaseView"; -import Switch from 'rc-switch'; +import { CollectionSchemaPreview } from "./CollectionSchemaView"; +import "./CollectionStackingView.scss"; +import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; +import { CollectionSubView } from "./CollectionSubView"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { @@ -32,13 +31,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { _columnStart: number = 0; @observable private cursor: CursorProperty = "grab"; get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } - @computed get chromeCollapsed() { return this.props.chromeCollapsed; } @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); } @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); } @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } @computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); } @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); } + @computed get sectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : ""; } get layoutDoc() { // if this document's layout field contains a document (ie, a rendering template), then we will use that @@ -46,37 +45,32 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } - @computed - get SectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : "" } get Sections() { - let sectionHeaders = this.sectionHeaders; - if (!sectionHeaders) { - this.props.Document.sectionHeaders = sectionHeaders = new List(); + if (!this.sectionFilter) return new Map(); + + if (this.sectionHeaders === undefined) { + this.props.Document.sectionHeaders = new List(); } - let fields = new Map(sectionHeaders.map(sh => [sh, []])); - if (this.SectionFilter) { - this.filteredChildren.map(d => { - let sectionValue = (d[this.SectionFilter] ? d[this.SectionFilter] : `NO ${this.SectionFilter.toUpperCase()} VALUE`) as object; - // the next five lines ensures that floating point rounding errors don't create more than one section -syip - let parsed = parseInt(sectionValue.toString()); - let castedSectionValue: any = sectionValue; - if (!isNaN(parsed)) { - castedSectionValue = parsed; - } + const sectionHeaders = this.sectionHeaders!; + let fields = new Map(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); + this.filteredChildren.map(d => { + let sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object; + // the next five lines ensures that floating point rounding errors don't create more than one section -syip + let parsed = parseInt(sectionValue.toString()); + let castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; - // look for if header exists already - let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`)); - if (existingHeader) { - fields.get(existingHeader)!.push(d); - } - else { - let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`); - fields.set(newSchemaHeader, [d]); - sectionHeaders!.push(newSchemaHeader); - } - }); - } else fields.clear(); + // look for if header exists already + let existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`)); + if (existingHeader) { + fields.get(existingHeader)!.push(d); + } + else { + let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`); + fields.set(newSchemaHeader, [d]); + sectionHeaders.push(newSchemaHeader); + } + }); return fields; } @@ -96,10 +90,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { // reset section headers when a new filter is inputted this._sectionFilterDisposer = reaction( - () => this.SectionFilter, - () => { - this.props.Document.sectionHeaders = new List(); - } + () => this.sectionFilter, + () => this.props.Document.sectionHeaders = new List() ); } componentWillUnmount() { @@ -186,8 +178,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - let targInd = -1; let where = [de.x, de.y]; + let targInd = -1; if (de.data instanceof DragManager.DocumentDragData) { this._docXfs.map((cd, i) => { let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); @@ -233,8 +225,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } }); } + headings = () => Array.from(this.Sections.keys()); section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { - let key = this.SectionFilter; + let key = this.sectionFilter; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { @@ -245,7 +238,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return Array.from(this.Sections.keys())} + headings={this.headings} heading={heading ? heading.heading : ""} headingObject={heading} docList={docList} @@ -254,16 +247,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { createDropTarget={this.createDropTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} />; - } @action addGroup = (value: string) => { - if (value) { - if (this.sectionHeaders) { - this.sectionHeaders.push(new SchemaHeaderField(value)); - return true; - } + if (value && this.sectionHeaders) { + this.sectionHeaders.push(new SchemaHeaderField(value)); + return true; } return false; } @@ -276,11 +266,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } onToggle = (checked: Boolean) => { - if (checked) { - this.props.CollectionView.props.Document.chromeStatus = 'collapsed'; - } else { - this.props.CollectionView.props.Document.chromeStatus = 'view-mode'; - } + this.props.CollectionView.props.Document.chromeSatus = checked ? "collapsed" : "view-mode"; } render() { @@ -296,10 +282,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return (
    e.stopPropagation()} > - {this.SectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). - map(section => this.section(section[0], section[1])) : + {this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). + map((section: [SchemaHeaderField, Doc[]]) => this.section(section[0], section[1])) : this.section(undefined, this.filteredChildren)} - {(this.SectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ? + {(this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
    diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 57dc5879b..f59fee985 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -20,16 +20,7 @@ import { CollectionTreeView } from "./CollectionTreeView"; import { CollectionViewBaseChrome } from './CollectionViewChromes'; export const COLLECTION_BORDER_WIDTH = 2; -library.add(faTh); -library.add(faTree); -library.add(faSquare); -library.add(faProjectDiagram); -library.add(faSignature); -library.add(faThList); -library.add(faFingerprint); -library.add(faColumns); -library.add(faEllipsisV); -library.add(faImage, faEye); +library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any); @observer export class CollectionView extends React.Component { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b4d065d26..c943fac74 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -40,7 +40,7 @@ import v5 = require("uuid/v5"); import { setScheduler } from "bluebird"; import { DocumentType, Docs } from "../../../documents/Documents"; -library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload); +library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload); export const panZoomSchema = createSchema({ panX: "number", @@ -206,8 +206,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(cd.width) + 2 * this._clusterDistance; let ch = NumCast(cd.height) + 2 * this._clusterDistance; - if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) + if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) { return NumCast(cd.cluster); + } return cluster; }, -1); if (cluster !== -1) { @@ -236,20 +237,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { updateClusters() { this.sets.length = 0; this.childDocs.map(c => { - let included = [] + let included = []; for (let i = 0; i < this.sets.length; i++) { - for (let j = 0; j < this.sets[i].length; j++) { - if (this.boundsOverlap(c, this.sets[i][j])) { + for (let member of this.sets[i]) { + if (this.boundsOverlap(c, member)) { included.push(i); break; } } } - if (included.length === 0) + if (included.length === 0) { this.sets.push([c]); - else if (included.length === 1) + } else if (included.length === 1) { this.sets[included[0]].push(c); - else { + } else { this.sets[included[0]].push(c); for (let s = 1; s < included.length; s++) { this.sets[included[0]].push(...this.sets[included[s]]); @@ -257,11 +258,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } }); - for (let s = 0; s < this.sets.length; s++) { - for (let i = 0; i < this.sets[s].length; i++) { - this.sets[s][i].cluster = s; - } - } + this.sets.map((set, i) => set.map(member => member.cluster = i)); } getClusterColor = (doc: Doc) => { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index e2c559c9a..640795789 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -16,7 +16,7 @@ import './ButtonBox.scss'; import { observer } from 'mobx-react'; import { DocumentIconContainer } from './DocumentIcon'; -library.add(faEdit); +library.add(faEdit as any); const ButtonSchema = createSchema({ onClick: ScriptField, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0347fc9b2..4528cf94d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -95,7 +95,7 @@ export interface DocumentViewProps { addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; zoomToScale: (scale: number) => void; - backgroundColor: (doc: Doc) => string; + backgroundColor: (doc: Doc) => string | undefined; getScale: () => number; animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; ChromeHeight?: () => number; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index fa072aecf..a49709e83 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -152,9 +152,7 @@ export class PDFBox extends DocComponent(PdfDocumen } scrollTo(y: number) { - if (this._mainCont.current) { - this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current!.offsetHeight / 2), 0), behavior: "auto" }); - } + this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); } settingsPanel() { diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 995ddd5c3..3e8582d61 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -384,7 +384,7 @@ export class FilterBox extends React.Component {
    Collection Filters Active
    : undefined}
    - ) + ); } // Useful queries: diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 1b9bba5c6..48eb87251 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -134,7 +134,7 @@ export class LinkContextMenu extends React.Component {
    - ) + ); } } diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx index a30104089..ed5ecd3ba 100644 --- a/src/client/views/search/ToggleBar.tsx +++ b/src/client/views/search/ToggleBar.tsx @@ -59,7 +59,7 @@ export class ToggleBar extends React.Component{ this._forwardTimeline.play(); this._forwardTimeline.reverse(); this.props.handleChange(); - console.log(this.props.getStatus()) + console.log(this.props.getStatus()); } @action.bound diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index b081588a3..c01f4e8cf 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -358,7 +358,7 @@ export namespace Doc { let docExtensionForField = doc[fieldKey + "_ext"] as Doc; if (docExtensionForField === undefined) { setTimeout(() => { - CreateDocumentExtensionForField(doc, field); + CreateDocumentExtensionForField(doc, fieldKey); }, 0); } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0); @@ -535,7 +535,7 @@ export namespace Doc { d.layout = detailLayout; d.nativeWidth = d.nativeHeight = undefined; if (detailLayout instanceof Doc) { - let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc; + let delegDetailLayout = Doc.MakeDelegate(detailLayout); d.layout = delegDetailLayout; delegDetailLayout.layout = await delegDetailLayout.detailedLayout; } -- cgit v1.2.3-70-g09d2 From 62e541e21b5675039283a515d9b1bba02b62e432 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 03:18:51 -0400 Subject: handled all edge cases for continuous recording --- src/client/util/DictationManager.ts | 70 +++++++++++++++++++++++------------- src/client/views/ContextMenuItem.tsx | 2 +- src/client/views/GlobalKeyHandler.ts | 8 +++-- 3 files changed, 52 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index fa34ca9ad..5f443f99e 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -38,14 +38,17 @@ export namespace DictationManager { } const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; - let isListening = false; - let isManuallyStopped = false; - const results: string[] = []; - const recognizer: SpeechRecognition = new webkitSpeechRecognition(); - export namespace Controls { - let newestResult: string; + const defaultDelimiter = "..."; + let isListening = false; + let isManuallyStopped = false; + let sessionResults: string[] = []; + + const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition(); + recognizer.onstart = () => console.log("initiating speech recognition session..."); + + let current: string | undefined = undefined; export type InterimResultHandler = (results: any) => any; export type ContinuityArgs = { indefinite: boolean } | false; export interface ListeningOptions { @@ -60,8 +63,7 @@ export namespace DictationManager { try { results = await listenImpl(options); } catch (e) { - results = "Dictation Error: "; - results += "error" in e ? e.error : "unknown error"; + results = `Dictation Error: ${"error" in e ? e.error : "unknown error"}`; } return results; }; @@ -74,6 +76,7 @@ export namespace DictationManager { let handler = options ? options.interimHandler : undefined; let continuous = options ? options.continuous : undefined; + let indefinite = continuous && continuous.indefinite; let language = options ? options.language : undefined; let delimiter = options ? options.delimiter : undefined; @@ -85,41 +88,56 @@ export namespace DictationManager { return new Promise((resolve, reject) => { - recognizer.onerror = (e: any) => { - reject(e); - stop(); + recognizer.onerror = (e: SpeechRecognitionError) => { + if (!(indefinite && e.error === "no-speech")) { + stop(true); + reject(e); + } }; recognizer.onresult = (e: SpeechRecognitionEvent) => { - newestResult = synthesize(e, delimiter); - continuous && continuous.indefinite && results.push(newestResult); - handler && handler(newestResult); + current = synthesize(e, delimiter); + handler && handler(current); + isManuallyStopped && complete(); }; recognizer.onend = (e: Event) => { - let indefinite = continuous && continuous.indefinite; - if (indefinite && !isManuallyStopped) { - recognizer.start(); + if (!indefinite || isManuallyStopped) { + return complete(); + } + + if (current) { + sessionResults.push(current); + current = undefined; + } + recognizer.start(); + }; + + let complete = () => { + if (indefinite) { + current && sessionResults.push(current); + resolve(connect(sessionResults, delimiter)); } else { - resolve(indefinite ? newestResult : results.join(delimiter)); - reset(); + resolve(current); } + reset(); }; }); }; - export const stop = (saveCumulative = true) => { - saveCumulative ? recognizer.stop() : recognizer.abort(); - reset(); + export const stop = (errorTriggered = false) => { + !errorTriggered && (isManuallyStopped = true); + recognizer.stop(); }; const reset = () => { isListening = false; isManuallyStopped = false; - recognizer.onerror = null; recognizer.onresult = null; + recognizer.onerror = null; recognizer.onend = null; + sessionResults = []; }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { @@ -128,7 +146,11 @@ export namespace DictationManager { for (let i = 0; i < results.length; i++) { transcripts.push(results.item(i).item(0).transcript.trim()); } - return transcripts.join(delimiter || "..."); + return transcripts.join(delimiter || defaultDelimiter); + }; + + const connect = (sessions: string[], delimiter?: string) => { + return sessions.map(text => `(${text})`).join(delimiter || defaultDelimiter); }; } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index a1787e78f..90f7be33f 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -39,13 +39,13 @@ export class ContextMenuItem extends React.Component) => { if ("event" in this.props) { + this.props.closeMenu && this.props.closeMenu(); let batch: UndoManager.Batch | undefined; if (this.props.undoable !== false) { batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); } await this.props.event(); batch && batch.end(); - this.props.closeMenu && this.props.closeMenu(); } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 98df43a1e..c3e6ae6c8 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -72,9 +72,11 @@ export default class KeyManager { main.toggleColorPicker(true); SelectionManager.DeselectAll(); DictationManager.Controls.stop(); - main.dictationOverlayVisible = false; - main.dictationSuccess = undefined; - main.cancelDictationFade(); + if (main.dictationOverlayVisible) { + main.dictationOverlayVisible = false; + main.dictationSuccess = undefined; + main.cancelDictationFade(); + } break; case "delete": case "backspace": -- cgit v1.2.3-70-g09d2 From 8d6620dd6818c82363ec0c4d49a92b0e2c0c1036 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 03:30:41 -0400 Subject: clean up --- src/client/util/DictationManager.ts | 46 +++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 5f443f99e..b6f871713 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -40,22 +40,27 @@ export namespace DictationManager { export namespace Controls { - const defaultDelimiter = "..."; + const intraSession = ". "; + const interSession = " ... "; + let isListening = false; let isManuallyStopped = false; + + let current: string | undefined = undefined; let sessionResults: string[] = []; const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition(); recognizer.onstart = () => console.log("initiating speech recognition session..."); - let current: string | undefined = undefined; export type InterimResultHandler = (results: any) => any; export type ContinuityArgs = { indefinite: boolean } | false; + export type DelimiterArgs = { inter: string, intra: string }; + export interface ListeningOptions { language: string; continuous: ContinuityArgs; + delimiters: DelimiterArgs; interimHandler: InterimResultHandler; - delimiter: string; } export const listen = async (options?: Partial) => { @@ -78,7 +83,8 @@ export namespace DictationManager { let continuous = options ? options.continuous : undefined; let indefinite = continuous && continuous.indefinite; let language = options ? options.language : undefined; - let delimiter = options ? options.delimiter : undefined; + let intra = options && options.delimiters ? options.delimiters.intra : undefined; + let inter = options && options.delimiters ? options.delimiters.inter : undefined; recognizer.interimResults = handler !== undefined; recognizer.continuous = continuous === undefined ? false : continuous !== false; @@ -90,13 +96,13 @@ export namespace DictationManager { recognizer.onerror = (e: SpeechRecognitionError) => { if (!(indefinite && e.error === "no-speech")) { - stop(true); + recognizer.stop(); reject(e); } }; recognizer.onresult = (e: SpeechRecognitionEvent) => { - current = synthesize(e, delimiter); + current = synthesize(e, intra); handler && handler(current); isManuallyStopped && complete(); }; @@ -116,7 +122,7 @@ export namespace DictationManager { let complete = () => { if (indefinite) { current && sessionResults.push(current); - resolve(connect(sessionResults, delimiter)); + resolve(sessionResults.join(inter || interSession)); } else { resolve(current); } @@ -126,18 +132,9 @@ export namespace DictationManager { }); }; - export const stop = (errorTriggered = false) => { - !errorTriggered && (isManuallyStopped = true); - recognizer.stop(); - }; - - const reset = () => { - isListening = false; - isManuallyStopped = false; - recognizer.onresult = null; - recognizer.onerror = null; - recognizer.onend = null; - sessionResults = []; + export const stop = (salvageSession = true) => { + isManuallyStopped = true; + salvageSession ? recognizer.stop() : recognizer.abort(); }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { @@ -146,11 +143,16 @@ export namespace DictationManager { for (let i = 0; i < results.length; i++) { transcripts.push(results.item(i).item(0).transcript.trim()); } - return transcripts.join(delimiter || defaultDelimiter); + return transcripts.join(delimiter || intraSession); }; - const connect = (sessions: string[], delimiter?: string) => { - return sessions.map(text => `(${text})`).join(delimiter || defaultDelimiter); + const reset = () => { + isListening = false; + isManuallyStopped = false; + recognizer.onresult = null; + recognizer.onerror = null; + recognizer.onend = null; + sessionResults = []; }; } -- cgit v1.2.3-70-g09d2 From 6d718c8a243e68d23199d35592bfded285385c91 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 05:09:56 -0400 Subject: now all listen() calls display recording UI --- src/client/util/DictationManager.ts | 24 ++++++++++++++++++++---- src/client/views/GlobalKeyHandler.ts | 21 ++++----------------- src/client/views/Main.scss | 1 - src/client/views/MainView.tsx | 11 ++++++++--- src/client/views/nodes/DocumentView.tsx | 16 +++++++++++++--- 5 files changed, 45 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b6f871713..a882994c1 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -10,6 +10,7 @@ import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; +import { MainView } from "../views/MainView"; /** * This namespace provides a singleton instance of a manager that @@ -37,9 +38,11 @@ export namespace DictationManager { } } const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; + export const placeholder = "Listening..."; export namespace Controls { + const infringe = "unable to process: dictation manager still involved in previous session"; const intraSession = ". "; const interSession = " ... "; @@ -52,7 +55,7 @@ export namespace DictationManager { const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition(); recognizer.onstart = () => console.log("initiating speech recognition session..."); - export type InterimResultHandler = (results: any) => any; + export type InterimResultHandler = (results: string) => any; export type ContinuityArgs = { indefinite: boolean } | false; export type DelimiterArgs = { inter: string, intra: string }; @@ -61,21 +64,33 @@ export namespace DictationManager { continuous: ContinuityArgs; delimiters: DelimiterArgs; interimHandler: InterimResultHandler; + tryExecute: boolean; } export const listen = async (options?: Partial) => { - let results: any; + let results: string | undefined; + MainView.Instance.dictationOverlayVisible = true; + MainView.Instance.isListening = true; try { results = await listenImpl(options); + if (results) { + MainView.Instance.isListening = false; + MainView.Instance.dictatedPhrase = results = results.toLowerCase(); + MainView.Instance.dictationSuccess = options && options.tryExecute ? await DictationManager.Commands.execute(results) : true; + } } catch (e) { - results = `Dictation Error: ${"error" in e ? e.error : "unknown error"}`; + MainView.Instance.isListening = false; + MainView.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + MainView.Instance.dictationSuccess = false; + } finally { + MainView.Instance.initiateDictationFade(); } return results; }; const listenImpl = (options?: Partial) => { if (isListening) { - return undefined; + return infringe; } isListening = true; @@ -126,6 +141,7 @@ export namespace DictationManager { } else { resolve(current); } + current = undefined; reset(); }; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index c3e6ae6c8..0989e8db1 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -73,9 +73,11 @@ export default class KeyManager { SelectionManager.DeselectAll(); DictationManager.Controls.stop(); if (main.dictationOverlayVisible) { + main.cancelDictationFade(); main.dictationOverlayVisible = false; + main.isListening = true; + main.dictatedPhrase = ""; main.dictationSuccess = undefined; - main.cancelDictationFade(); } break; case "delete": @@ -110,22 +112,7 @@ export default class KeyManager { switch (keyname) { case " ": - let main = MainView.Instance; - main.dictationOverlayVisible = true; - - main.isListening = true; - // let printer = (results: any) => console.log(results); - let command = await DictationManager.Controls.listen(); - main.isListening = false; - - if (!command) { - break; - } - - main.dictatedPhrase = command = command.toLowerCase(); - main.dictationSuccess = await DictationManager.Commands.execute(command); - main.initiateDictationFade(); - + DictationManager.Controls.listen({ tryExecute: true }); stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 8e57b88c3..f76abaff3 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -292,7 +292,6 @@ ul#add-options-list { width: 100%; height: 100%; position: absolute; - background: darkslategray; z-index: 999; transition: 0.5s all ease; pointer-events: none; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4443eea6d..383efa1e3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,7 +1,7 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faPlay, faPause, faCaretUp, faLongArrowAltRight, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat, faBolt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx'; +import { action, computed, configure, observable, runInAction, reaction, trace, autorun } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; @@ -167,6 +167,8 @@ export class MainView extends React.Component { } } + autorun(() => console.log(`this.isListening = ${this.isListening}`)); + library.add(faFont); library.add(faExclamation); library.add(faPortrait); @@ -523,7 +525,7 @@ export class MainView extends React.Component { render() { let display = this.dictationOverlayVisible; let success = this.dictationSuccess; - let result = this.isListening ? "Listening..." : `"${this.dictatedPhrase}"`; + let result = this.isListening ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; return (
    {result}
    {this.mainContent} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1d9cb3c80..a415aefda 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -38,6 +38,7 @@ import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); import { DictationManager } from '../../util/DictationManager'; +import { MainView } from '../MainView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -536,9 +537,18 @@ export class DocumentView extends DocComponent(Docu } listen = async () => { - let options = { continuous: { indefinite: true }, delimiter: " " }; - let transcript = await DictationManager.Controls.listen(options); - transcript && (Doc.GetProto(this.props.Document).transcript = transcript); + let dataDoc = Doc.GetProto(this.props.Document); + let options = { + continuous: { indefinite: true }, + delimiter: " ", + interimHandler: (results: string) => { + MainView.Instance.isListening = false; + MainView.Instance.dictationSuccess = true; + MainView.Instance.dictatedPhrase = results; + } + }; + let final = await DictationManager.Controls.listen(options); + final && (dataDoc.transcript = final); } @action -- cgit v1.2.3-70-g09d2 From 1ed381022450bc5c39238c73f732179f4b21daf2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 05:13:51 -0400 Subject: clean up --- src/client/views/nodes/DocumentView.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a415aefda..458fb582d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -538,16 +538,15 @@ export class DocumentView extends DocComponent(Docu listen = async () => { let dataDoc = Doc.GetProto(this.props.Document); - let options = { - continuous: { indefinite: true }, - delimiter: " ", - interimHandler: (results: string) => { - MainView.Instance.isListening = false; - MainView.Instance.dictationSuccess = true; - MainView.Instance.dictatedPhrase = results; - } + let handler = (results: string) => { + MainView.Instance.isListening = false; + MainView.Instance.dictationSuccess = true; + MainView.Instance.dictatedPhrase = results; }; - let final = await DictationManager.Controls.listen(options); + let final = await DictationManager.Controls.listen({ + continuous: { indefinite: true }, + interimHandler: handler + }); final && (dataDoc.transcript = final); } -- cgit v1.2.3-70-g09d2 From 95ff1649631457449bdc580a5a47937718c00e3b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 05:19:14 -0400 Subject: factored out stop UI code --- src/client/util/DictationManager.ts | 10 ++++++++++ src/client/views/GlobalKeyHandler.ts | 7 ------- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index a882994c1..2af7c53cb 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -149,8 +149,18 @@ export namespace DictationManager { }; export const stop = (salvageSession = true) => { + if (!isListening) { + return; + } isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); + if (MainView.Instance.dictationOverlayVisible) { + MainView.Instance.cancelDictationFade(); + MainView.Instance.dictationOverlayVisible = false; + MainView.Instance.isListening = true; + MainView.Instance.dictatedPhrase = ""; + MainView.Instance.dictationSuccess = undefined; + } }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 0989e8db1..e773014e3 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -72,13 +72,6 @@ export default class KeyManager { main.toggleColorPicker(true); SelectionManager.DeselectAll(); DictationManager.Controls.stop(); - if (main.dictationOverlayVisible) { - main.cancelDictationFade(); - main.dictationOverlayVisible = false; - main.isListening = true; - main.dictatedPhrase = ""; - main.dictationSuccess = undefined; - } break; case "delete": case "backspace": -- cgit v1.2.3-70-g09d2 From fd047fa87334b948a4a0b00ccf9bd02ded03ea36 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 4 Aug 2019 10:16:46 -0400 Subject: fixed MakeLink (& text linking) --- src/client/documents/Documents.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 07e38a4c0..e804d5440 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -602,9 +602,8 @@ export namespace DocUtils { if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; if (target === CurrentUserUtils.UserDocument) return undefined; - let linkDoc: Doc | undefined; + let linkDocProto = new Doc(); UndoManager.RunInBatch(() => { - let linkDocProto = new Doc(); linkDocProto.type = DocumentType.LINK; linkDocProto.targetContext = targetContext; @@ -628,7 +627,7 @@ export namespace DocUtils { computed.compiled && (Doc.GetProto(source).links = new ComputedField(computed)); computed.compiled && (Doc.GetProto(target).links = new ComputedField(computed)); }, "make link"); - return linkDoc; + return linkDocProto; } } -- cgit v1.2.3-70-g09d2 From 0010f88e1002feb14ecfb111c2c6ae56ee34cf2d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 13:32:19 -0400 Subject: clean up and UI fix --- src/client/util/DictationManager.ts | 43 +++++++++++++++++++-------------- src/client/views/MainView.tsx | 11 ++++----- src/client/views/nodes/DocumentView.tsx | 20 ++++++--------- 3 files changed, 38 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 2af7c53cb..ee1f11b4f 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -58,6 +58,7 @@ export namespace DictationManager { export type InterimResultHandler = (results: string) => any; export type ContinuityArgs = { indefinite: boolean } | false; export type DelimiterArgs = { inter: string, intra: string }; + export type ListeningUIStatus = { interim: boolean } | false; export interface ListeningOptions { language: string; @@ -69,22 +70,28 @@ export namespace DictationManager { export const listen = async (options?: Partial) => { let results: string | undefined; - MainView.Instance.dictationOverlayVisible = true; - MainView.Instance.isListening = true; + let main = MainView.Instance; + + main.dictationOverlayVisible = true; + let interim = options !== undefined && options.interimHandler !== undefined; + main.isListening = { interim: interim }; + try { results = await listenImpl(options); if (results) { - MainView.Instance.isListening = false; - MainView.Instance.dictatedPhrase = results = results.toLowerCase(); - MainView.Instance.dictationSuccess = options && options.tryExecute ? await DictationManager.Commands.execute(results) : true; + main.isListening = false; + main.dictatedPhrase = results; + let execute = options && options.tryExecute; + main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; } } catch (e) { - MainView.Instance.isListening = false; - MainView.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; - MainView.Instance.dictationSuccess = false; + main.isListening = false; + main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + main.dictationSuccess = false; } finally { - MainView.Instance.initiateDictationFade(); + main.initiateDictationFade(); } + return results; }; @@ -137,11 +144,10 @@ export namespace DictationManager { let complete = () => { if (indefinite) { current && sessionResults.push(current); - resolve(sessionResults.join(inter || interSession)); + sessionResults.length && resolve(sessionResults.join(inter || interSession)); } else { resolve(current); } - current = undefined; reset(); }; @@ -154,12 +160,12 @@ export namespace DictationManager { } isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); - if (MainView.Instance.dictationOverlayVisible) { - MainView.Instance.cancelDictationFade(); - MainView.Instance.dictationOverlayVisible = false; - MainView.Instance.isListening = true; - MainView.Instance.dictatedPhrase = ""; - MainView.Instance.dictationSuccess = undefined; + let main = MainView.Instance; + if (main.dictationOverlayVisible) { + main.cancelDictationFade(); + main.dictationOverlayVisible = false; + main.dictationSuccess = undefined; + setTimeout(() => main.dictatedPhrase = placeholder, 500); } }; @@ -173,12 +179,13 @@ export namespace DictationManager { }; const reset = () => { + current = undefined; + sessionResults = []; isListening = false; isManuallyStopped = false; recognizer.onresult = null; recognizer.onerror = null; recognizer.onend = null; - sessionResults = []; }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 383efa1e3..631d24cb1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,10 +49,10 @@ export class MainView extends React.Component { @observable public pwidth: number = 0; @observable public pheight: number = 0; - @observable private dictationState = ""; + @observable private dictationState = DictationManager.placeholder; @observable private dictationSuccessState: boolean | undefined = undefined; @observable private dictationDisplayState = false; - @observable private dictationListeningState = false; + @observable private dictationListeningState: DictationManager.Controls.ListeningUIStatus = false; public overlayTimeout: NodeJS.Timeout | undefined; @@ -61,6 +61,7 @@ export class MainView extends React.Component { this.overlayTimeout = setTimeout(() => { this.dictationOverlayVisible = false; this.dictationSuccess = undefined; + setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500); }, duration); } @@ -116,7 +117,7 @@ export class MainView extends React.Component { return this.dictationListeningState; } - public set isListening(value: boolean) { + public set isListening(value: DictationManager.Controls.ListeningUIStatus) { runInAction(() => this.dictationListeningState = value); } @@ -167,8 +168,6 @@ export class MainView extends React.Component { } } - autorun(() => console.log(`this.isListening = ${this.isListening}`)); - library.add(faFont); library.add(faExclamation); library.add(faPortrait); @@ -525,7 +524,7 @@ export class MainView extends React.Component { render() { let display = this.dictationOverlayVisible; let success = this.dictationSuccess; - let result = this.isListening ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; + let result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; return (
    (Docu this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; } - listen = async () => { - let dataDoc = Doc.GetProto(this.props.Document); - let handler = (results: string) => { - MainView.Instance.isListening = false; - MainView.Instance.dictationSuccess = true; - MainView.Instance.dictatedPhrase = results; - }; - let final = await DictationManager.Controls.listen({ + listen = async () => + Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, - interimHandler: handler - }); - final && (dataDoc.transcript = final); - } + interimHandler: (results: string) => { + MainView.Instance.isListening = { interim: true }; + MainView.Instance.dictationSuccess = true; + MainView.Instance.dictatedPhrase = results; + } + }) @action onContextMenu = async (e: React.MouseEvent): Promise => { -- cgit v1.2.3-70-g09d2 From 0b2d0cc563755c7b5899bc25a791359306166d9b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 4 Aug 2019 13:34:29 -0400 Subject: fixed up backend for titles and captions. --- src/client/views/nodes/DocumentView.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4528cf94d..a7b4f33db 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -690,8 +690,8 @@ export class DocumentView extends DocComponent(Docu let templates = Cast(this.layoutDoc.templates, listSpec("string")); if (!showOverlays && templates instanceof List) { templates.map(str => { - if (str.indexOf("{props.Document.title}") !== -1) showTitle = "title"; - if (str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption"; + if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title"; + if (!showCaption && str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption"; }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" Date: Sun, 4 Aug 2019 14:04:22 -0400 Subject: final UI tweaks --- src/client/util/DictationManager.ts | 5 ++--- src/client/views/MainView.tsx | 8 ++++---- src/client/views/nodes/DocumentView.tsx | 12 +++++++----- 3 files changed, 13 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index ee1f11b4f..978889830 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -73,15 +73,14 @@ export namespace DictationManager { let main = MainView.Instance; main.dictationOverlayVisible = true; - let interim = options !== undefined && options.interimHandler !== undefined; - main.isListening = { interim: interim }; + main.isListening = { interim: false }; try { results = await listenImpl(options); if (results) { main.isListening = false; - main.dictatedPhrase = results; let execute = options && options.tryExecute; + main.dictatedPhrase = execute ? results.toLowerCase() : results; main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; } } catch (e) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 631d24cb1..618e83bfa 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -89,7 +89,7 @@ export class MainView extends React.Component { } } - public get dictatedPhrase() { + @computed public get dictatedPhrase() { return this.dictationState; } @@ -97,7 +97,7 @@ export class MainView extends React.Component { runInAction(() => this.dictationState = value); } - public get dictationSuccess() { + @computed public get dictationSuccess() { return this.dictationSuccessState; } @@ -105,7 +105,7 @@ export class MainView extends React.Component { runInAction(() => this.dictationSuccessState = value); } - public get dictationOverlayVisible() { + @computed public get dictationOverlayVisible() { return this.dictationDisplayState; } @@ -113,7 +113,7 @@ export class MainView extends React.Component { runInAction(() => this.dictationDisplayState = value); } - public get isListening() { + @computed public get isListening() { return this.dictationListeningState; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d6ae17537..57e66ff1b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -536,15 +536,17 @@ export class DocumentView extends DocComponent(Docu this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; } - listen = async () => + listen = async () => { Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, interimHandler: (results: string) => { - MainView.Instance.isListening = { interim: true }; - MainView.Instance.dictationSuccess = true; - MainView.Instance.dictatedPhrase = results; + let main = MainView.Instance; + main.dictationSuccess = true; + main.dictatedPhrase = results; + main.isListening = { interim: true }; } - }) + }); + } @action onContextMenu = async (e: React.MouseEvent): Promise => { -- cgit v1.2.3-70-g09d2 From ac6d9fe0b327436729ceeaee2acecf6ea8f2fa3d Mon Sep 17 00:00:00 2001 From: kimdahey Date: Sun, 4 Aug 2019 20:53:50 -0400 Subject: copy to clipboard --- .vscode/launch.json | 3 +++ package.json | 3 ++- src/client/util/DictationManager.ts | 12 +++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/.vscode/launch.json b/.vscode/launch.json index e4196600e..d2c18d6f1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,9 @@ "breakOnLoad": true, "url": "http://localhost:1050/login", "webRoot": "${workspaceFolder}", + "runtimeArgs": [ + "--experimental-modules" + ] }, { "type": "firefox", diff --git a/package.json b/package.json index 3d0d5c3d7..97980f0ca 100644 --- a/package.json +++ b/package.json @@ -117,8 +117,8 @@ "bluebird": "^3.5.3", "body-parser": "^1.18.3", "bootstrap": "^4.3.1", - "child_process": "^1.0.2", "canvas": "^2.5.0", + "child_process": "^1.0.2", "class-transformer": "^0.2.0", "connect-flash": "^0.1.1", "connect-mongo": "^2.0.3", @@ -211,6 +211,7 @@ "socket.io-client": "^2.2.0", "solr-node": "^1.2.1", "standard-http-error": "^2.0.1", + "ts-clipboard": "^1.0.17", "typescript-collections": "^1.3.2", "url-loader": "^1.1.2", "uuid": "^3.3.2", diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 978889830..3668ca0d2 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,6 +11,7 @@ import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { MainView } from "../views/MainView"; +import { Clipboard } from "ts-clipboard"; /** * This namespace provides a singleton instance of a manager that @@ -78,6 +79,7 @@ export namespace DictationManager { try { results = await listenImpl(options); if (results) { + Clipboard.copy(results); main.isListening = false; let execute = options && options.tryExecute; main.dictatedPhrase = execute ? results.toLowerCase() : results; @@ -250,7 +252,8 @@ export namespace DictationManager { [DocumentType.AUDIO, AudioField], [DocumentType.IMG, ImageField], [DocumentType.HIST, HistogramField], - [DocumentType.IMPORT, listSpec(Doc)] + [DocumentType.IMPORT, listSpec(Doc)], + [DocumentType.TEXT, "string"] ]); const tryCast = (view: DocumentView, type: DocumentType) => { @@ -294,6 +297,13 @@ export namespace DictationManager { let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); target.props.addDocTab(kvp, target.dataDoc, "onRight"); } + }], + + ["promote", { + action: (target: DocumentView) => { + console.log(target); + }, + restrictTo: [DocumentType.TEXT] }] ]); -- cgit v1.2.3-70-g09d2 From a8aa834d8e08bddde705ce90ef4dee8be3791862 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 4 Aug 2019 21:56:11 -0400 Subject: fixed dropping of PDFs (or anything that doesn't have a height). --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c943fac74..2bdbb6749 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -69,7 +69,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) { let bounds = boundsList.reduce((bounds, b) => { var [sptX, sptY] = [b.x, b.y]; - let [bptX, bptY] = [sptX + b.width, sptY + b.height]; + let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)]; return { x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) @@ -617,7 +617,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ele: , - bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined + bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined }); } } -- cgit v1.2.3-70-g09d2 From ba2628e268e0f024fa9975e3594b6e218b5dc9c9 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 22:08:41 -0400 Subject: buxton's new docs --- src/.DS_Store | Bin 6148 -> 6148 bytes .../source/Bill_Notes_3_button_optical_mouse.docx | Bin 0 -> 412208 bytes .../source/Bill_Notes_Amazon_Kindle_Keyboard.docx | Bin 0 -> 474022 bytes .../buxton/source/Bill_Notes_Apple_Adj_Keyboard.docx | Bin 0 -> 1758498 bytes src/scraping/buxton/source/Bill_Notes_BAT.docx | Bin 0 -> 1349620 bytes .../buxton/source/Bill_Notes_Casio_CZ-101.docx | Bin 0 -> 523939 bytes .../source/Bill_Notes_Kindle_3G_lighted_cover.docx | Bin 0 -> 919789 bytes .../buxton/source/Bill_Notes_Microwriter.docx | Bin 0 -> 1042556 bytes src/scraping/buxton/source/Bill_Notes_Twiddler.docx | Bin 0 -> 526307 bytes src/scraping/buxton/source/Bill_Notes_orbiTouch.doc | Bin 0 -> 3945306 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/scraping/buxton/source/Bill_Notes_3_button_optical_mouse.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Amazon_Kindle_Keyboard.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Apple_Adj_Keyboard.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_BAT.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Casio_CZ-101.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Kindle_3G_lighted_cover.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Microwriter.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Twiddler.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_orbiTouch.doc (limited to 'src') diff --git a/src/.DS_Store b/src/.DS_Store index c544bc837..5b35884bd 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/scraping/buxton/source/Bill_Notes_3_button_optical_mouse.docx b/src/scraping/buxton/source/Bill_Notes_3_button_optical_mouse.docx new file mode 100644 index 000000000..a2ab04b78 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_3_button_optical_mouse.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Amazon_Kindle_Keyboard.docx b/src/scraping/buxton/source/Bill_Notes_Amazon_Kindle_Keyboard.docx new file mode 100644 index 000000000..e4375ebeb Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Amazon_Kindle_Keyboard.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Apple_Adj_Keyboard.docx b/src/scraping/buxton/source/Bill_Notes_Apple_Adj_Keyboard.docx new file mode 100644 index 000000000..99f7ad19d Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Apple_Adj_Keyboard.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_BAT.docx b/src/scraping/buxton/source/Bill_Notes_BAT.docx new file mode 100644 index 000000000..0e3368611 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_BAT.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Casio_CZ-101.docx b/src/scraping/buxton/source/Bill_Notes_Casio_CZ-101.docx new file mode 100644 index 000000000..c8d3943c0 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Casio_CZ-101.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Kindle_3G_lighted_cover.docx b/src/scraping/buxton/source/Bill_Notes_Kindle_3G_lighted_cover.docx new file mode 100644 index 000000000..f00fcb772 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Kindle_3G_lighted_cover.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Microwriter.docx b/src/scraping/buxton/source/Bill_Notes_Microwriter.docx new file mode 100644 index 000000000..3ac272e42 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Microwriter.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Twiddler.docx b/src/scraping/buxton/source/Bill_Notes_Twiddler.docx new file mode 100644 index 000000000..27b4acc85 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Twiddler.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_orbiTouch.doc b/src/scraping/buxton/source/Bill_Notes_orbiTouch.doc new file mode 100644 index 000000000..6bd71f20e Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_orbiTouch.doc differ -- cgit v1.2.3-70-g09d2 From 16eabe68912ec4e48fc4059f9b377fba0f076ab1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 5 Aug 2019 00:43:49 -0400 Subject: a couple of fixes to PDF linking and scrolling --- src/client/views/pdf/Annotation.scss | 4 +++ src/client/views/pdf/Annotation.tsx | 19 +++++----- src/client/views/pdf/PDFAnnotationLayer.scss | 6 ++++ src/client/views/pdf/PDFAnnotationLayer.tsx | 7 ++-- src/client/views/pdf/PDFViewer.scss | 9 +++-- src/client/views/pdf/PDFViewer.tsx | 52 +++++++++++----------------- 6 files changed, 48 insertions(+), 49 deletions(-) create mode 100644 src/client/views/pdf/Annotation.scss create mode 100644 src/client/views/pdf/PDFAnnotationLayer.scss (limited to 'src') diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss new file mode 100644 index 000000000..0ea85d522 --- /dev/null +++ b/src/client/views/pdf/Annotation.scss @@ -0,0 +1,4 @@ +.pdfViewer-annotationBox { + pointer-events: all; + user-select: none; +} \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 513f9fed6..a08ff5969 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,16 +1,15 @@ import React = require("react"); -import { Doc, DocListCast, WidthSym, HeightSym } from "../../../new_fields/Doc"; -import { AnnotationTypes, Viewer, scale } from "./PDFViewer"; +import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { observable, IReactionDisposer, reaction, action } from "mobx"; -import { BoolCast, NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; +import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; -import PDFMenu from "./PDFMenu"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; import { PresentationView } from "../presentationview/PresentationView"; -import { LinkManager } from "../../util/LinkManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; +import PDFMenu from "./PDFMenu"; +import "./Annotation.scss"; +import { AnnotationTypes, scale, Viewer } from "./PDFViewer"; interface IAnnotationProps { anno: Doc; @@ -118,8 +117,9 @@ class RegionAnnotation extends React.Component { if (targetDoc) { let context = await Cast(targetDoc.targetContext, Doc); if (context) { - DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, - ((doc) => this.props.parent.props.parent.props.addDocTab(context!, context!.proto, e.ctrlKey ? "onRight" : "inTab"))); + DocumentManager.Instance.jumpToDocument(targetDoc, false, false, + ((doc) => this.props.parent.props.parent.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")), + undefined, undefined); } } } @@ -151,7 +151,6 @@ class RegionAnnotation extends React.Component { left: this.props.x * scale, width: this.props.width * scale, height: this.props.height * scale, - pointerEvents: "all", backgroundColor: this.props.parent.Index === this.props.index ? "green" : StrCast(this.props.document.color) }}>
    ); diff --git a/src/client/views/pdf/PDFAnnotationLayer.scss b/src/client/views/pdf/PDFAnnotationLayer.scss new file mode 100644 index 000000000..733533007 --- /dev/null +++ b/src/client/views/pdf/PDFAnnotationLayer.scss @@ -0,0 +1,6 @@ +.pdfAnnotationLayer-cont { + width:100%; + height:100%; + position:relative; + top:-200%; +} \ No newline at end of file diff --git a/src/client/views/pdf/PDFAnnotationLayer.tsx b/src/client/views/pdf/PDFAnnotationLayer.tsx index 1f49e0d2f..4f267a5c0 100644 --- a/src/client/views/pdf/PDFAnnotationLayer.tsx +++ b/src/client/views/pdf/PDFAnnotationLayer.tsx @@ -1,5 +1,6 @@ import React = require("react"); import { observer } from "mobx-react"; +import "./PDFAnnotationLayer.scss"; interface IAnnotationProps { @@ -15,10 +16,6 @@ export class PDFAnnotationLayer extends React.Component { } render() { - return ( -
    - -
    - ); + return
    ; } } \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 0fde764d0..7158aaffa 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -27,9 +27,12 @@ // position: absolute; // top: 0; } - +.pdfViewere-viewer { + pointer-events:inherit; +} .pdfViewer-text { - + transform: scale(1.5); + transform-origin: top left; .page { .canvasWrapper { display: none; @@ -120,6 +123,8 @@ .pdfViewer-annotationLayer { position: absolute; top: 0; + width: 100%; + pointer-events: none; } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 5eb02a6da..6a99cec59 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -296,19 +296,19 @@ export class Viewer extends React.Component { } drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc = de.data.linkSourceDocument; - let destDoc = this.makeAnnotationDocument(sourceDoc, 1, "red"); - de.data.droppedDocuments.push(destDoc); - let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); - if (targetAnnotations) { - targetAnnotations.push(destDoc); - } - else { - this.props.parent.fieldExtensionDoc.annotations = new List([destDoc]); - } - e.stopPropagation(); - } + // if (de.data instanceof DragManager.LinkDragData) { + // let sourceDoc = de.data.linkSourceDocument; + // let destDoc = this.makeAnnotationDocument(sourceDoc, 1, "red"); + // de.data.droppedDocuments.push(destDoc); + // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); + // if (targetAnnotations) { + // targetAnnotations.push(destDoc); + // } + // else { + // this.props.parent.fieldExtensionDoc.annotations = new List([destDoc]); + // } + // e.stopPropagation(); + // } } /** * Called by the Page class when it gets rendered, initializes the lists and @@ -652,16 +652,12 @@ export class Viewer extends React.Component { render() { let compiled = this._script; return ( -
    +
    {this._visibleElements}
    -
    console.log("gello world")} style={{ transform: "scale(1.5)", transformOrigin: "top left" }} /> -
    +
    +
    {this._annotations.filter(anno => { if (compiled && compiled.compiled) { @@ -731,20 +727,12 @@ class SimpleLinkService { cachePageRef() { } - get pagesCount() { - return this.pdf ? this.pdf.numPages : 0; - } + get pagesCount() { return this.pdf ? this.pdf.numPages : 0; } - get page() { - return 0; - } + get page() { return 0; } - setPdf(pdf: any) { - this.pdf = pdf; - } + setPdf(pdf: any) { this.pdf = pdf; } - get rotation() { - return 0; - } + get rotation() { return 0; } set rotation(value: any) { } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 51a0fa4a803cea526711ef193f4690721d8b2f66 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 5 Aug 2019 08:56:08 -0400 Subject: added annotationOn to pdf anchors --- src/client/views/pdf/Page.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index c5b2a1dda..dea4e0da1 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -5,7 +5,7 @@ import "pdfjs-dist/web/pdf_viewer.css"; import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { PDFBox } from "../nodes/PDFBox"; @@ -159,6 +159,7 @@ export default class Page extends React.Component { let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); targetDoc.targetPage = this.props.page; let annotationDoc = this.highlight(undefined, "red"); + Doc.GetProto(annotationDoc).annotationOn = thisDoc; annotationDoc.linkedToDoc = false; // create dragData and star tdrag let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc); @@ -166,13 +167,9 @@ export default class Page extends React.Component { DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { handlers: { dragComplete: async () => { - if (!(await annotationDoc.linkedToDoc)) { + if (!BoolCast(annotationDoc.linkedToDoc)) { let annotations = await DocListCastAsync(annotationDoc.annotations); - if (annotations) { - annotations.forEach(anno => { - anno.target = targetDoc; - }); - } + annotations && annotations.forEach(anno => anno.target = targetDoc); let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc); if (pdfDoc) { DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); -- cgit v1.2.3-70-g09d2 From 298d1c9b29d6ce2171fd9ac8274b64583b73f6f5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 5 Aug 2019 18:32:53 -0400 Subject: added reset view menu option --- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2bdbb6749..a08a12426 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; +import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc"; @@ -36,11 +36,9 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import v5 = require("uuid/v5"); -import { setScheduler } from "bluebird"; import { DocumentType, Docs } from "../../../documents/Documents"; -library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload); +library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); export const panZoomSchema = createSchema({ panX: "number", @@ -651,6 +649,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { event: async () => this.props.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ + description: "reset view", event: () => { + this.props.Document.panX = this.props.Document.panY = 0; + this.props.Document.scale = 1; + }, icon: "compress-arrows-alt" + }); layoutItems.push({ description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: async () => { @@ -658,12 +662,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; this.props.Document.useClusters = !this.props.Document.useClusters; }, - icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt" + icon: !this.props.Document.useClusters ? "braille" : "braille" }); layoutItems.push({ description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, - icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt" + icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" }); layoutItems.push({ description: "Arrange contents in grid", -- cgit v1.2.3-70-g09d2 From 2978e805156bffad92e8ac23259843a7cf207fb7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 6 Aug 2019 11:11:26 -0400 Subject: beginning link to doc in context --- src/client/util/DictationManager.ts | 4 ++-- src/client/views/nodes/DocumentView.tsx | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 3668ca0d2..9c61fe125 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,7 +11,7 @@ import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { MainView } from "../views/MainView"; -import { Clipboard } from "ts-clipboard"; +import { Utils } from "../../Utils"; /** * This namespace provides a singleton instance of a manager that @@ -79,7 +79,7 @@ export namespace DictationManager { try { results = await listenImpl(options); if (results) { - Clipboard.copy(results); + Utils.CopyText(results); main.isListening = false; let execute = options && options.tryExecute; main.dictatedPhrase = execute ? results.toLowerCase() : results; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 57e66ff1b..e5b0b0b52 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -602,6 +602,14 @@ export class DocumentView extends DocComponent(Docu if (!ClientUtils.RELEASE) { let copies: ContextMenuProps[] = []; copies.push({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" }); + copies.push({ + description: "Copy Context", event: () => { + let parent = this.props.ContainingCollectionView; + if (parent) { + Utils.CopyText(Utils.prepend("/doc/" + parent.props.Document[Id])); + } + }, icon: "link" + }); copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" }); } -- cgit v1.2.3-70-g09d2 From 572c4196e0f41ec6bae8cae403812f9b97d5a3c7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 6 Aug 2019 13:27:47 -0400 Subject: post factored out, buxton pivot viewer route --- src/Utils.ts | 11 +++++++++++ src/client/views/nodes/DocumentView.tsx | 1 + src/server/index.ts | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 502540eb0..c1737a084 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,6 +3,7 @@ import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; import { Message } from './server/Message'; import { RouteStore } from './server/RouteStore'; +import requestPromise = require('request-promise'); export class Utils { @@ -173,4 +174,14 @@ export namespace JSONUtils { return results; } +} + +export function PostToServer(relativeRoute: string, body: any) { + let options = { + method: "POST", + uri: Utils.prepend(relativeRoute), + json: true, + body: body + }; + return requestPromise.post(options); } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 547d858ce..dbbf95479 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -39,6 +39,7 @@ import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); import { DictationManager } from '../../util/DictationManager'; import { MainView } from '../MainView'; +import requestPromise = require('request-promise'); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); diff --git a/src/server/index.ts b/src/server/index.ts index 10a84c823..1e811c1b2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -148,6 +148,25 @@ app.get("/pull", (req, res) => res.redirect("/"); })); +app.get("/buxton/:clear", (req, res) => { + if (req.params.clear === "true") { + deleteFields().then(() => upload_buxton_docs(res)); + } else { + upload_buxton_docs(res); + } +}); + +let upload_buxton_docs = (res: Response) => { + let buxton_scraping = path.join(__dirname, '../scraping/buxton'); + exec('python scraper.py', { cwd: buxton_scraping }, (err, stdout, sterr) => { + if (err) { + res.send(err.message); + return; + } + res.redirect("/"); + }); +}; + app.get("/version", (req, res) => { exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => { if (err) { -- cgit v1.2.3-70-g09d2 From 9419693f37ad53fcc0763a118d2cb865659a2ff4 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 6 Aug 2019 14:08:59 -0400 Subject: multiple changes to PDF related code -- mostly clean up --- src/client/views/collections/CollectionPDFView.tsx | 61 +-- src/client/views/nodes/PDFBox.tsx | 241 +++++------- src/client/views/pdf/Annotation.tsx | 69 ++-- src/client/views/pdf/PDFMenu.tsx | 123 +++--- src/client/views/pdf/PDFViewer.scss | 14 +- src/client/views/pdf/PDFViewer.tsx | 412 ++++++--------------- src/client/views/pdf/Page.tsx | 246 +++--------- 7 files changed, 389 insertions(+), 777 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 70010819a..3736ebada 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,36 +1,31 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, IReactionDisposer, observable, reaction, computed } from "mobx"; import { observer } from "mobx-react"; -import { WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { NumCast } from "../../../new_fields/Types"; import { emptyFunction } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import { PDFBox } from "../nodes/PDFBox"; import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPDFView.scss"; import React = require("react"); -import { PDFBox } from "../nodes/PDFBox"; @observer export class CollectionPDFView extends React.Component { + public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") { + return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt); + } + private _pdfBox?: PDFBox; private _reactionDisposer?: IReactionDisposer; - private _buttonTray: React.RefObject; - - constructor(props: FieldViewProps) { - super(props); - - this._buttonTray = React.createRef(); - } + private _buttonTray: React.RefObject = React.createRef(); componentDidMount() { this._reactionDisposer = reaction( () => NumCast(this.props.Document.scrollY), - () => { - this.props.Document.panY = NumCast(this.props.Document.scrollY); - }, + () => this.props.Document.panY = NumCast(this.props.Document.scrollY), { fireImmediately: true } ); } @@ -39,28 +34,12 @@ export class CollectionPDFView extends React.Component { this._reactionDisposer && this._reactionDisposer(); } - public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") { - return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt); - } - @observable _inThumb = false; - - private set curPage(value: number) { this._pdfBox && this._pdfBox.GotoPage(value); } - private get curPage() { return NumCast(this.props.Document.curPage, -1); } - private get numPages() { return NumCast(this.props.Document.numPages); } - @action onPageBack = () => this._pdfBox && this._pdfBox.BackPage(); - @action onPageForward = () => this._pdfBox && this._pdfBox.ForwardPage(); - - nativeWidth = () => NumCast(this.props.Document.nativeWidth); - nativeHeight = () => NumCast(this.props.Document.nativeHeight); - private get uIButtons() { - let ratio = (this.curPage - 1) / this.numPages * 100; + @computed + get uIButtons() { return (
    - - - {/*
    -
    -
    */} + +
    ); } @@ -73,20 +52,16 @@ export class CollectionPDFView extends React.Component { setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; }; - - private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; - return ( - <> - - {renderProps.active() ? this.uIButtons : (null)} - - ); + subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + return (<> + + {renderProps.active() ? this.uIButtons : (null)} + ); } render() { return ( - + {this.subView} ); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index a49709e83..56e720bf7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,31 +1,24 @@ -import { action, IReactionDisposer, observable, reaction, trace, untracked, computed } from 'mobx'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; +import * as Pdfjs from "pdfjs-dist"; +import "pdfjs-dist/web/pdf_viewer.css"; import 'react-image-lightbox/style.css'; -import { WidthSym, Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym, Opt } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, BoolCast } from "../../../new_fields/Types"; +import { ScriptField } from '../../../new_fields/ScriptField'; +import { BoolCast, Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; -//@ts-ignore -// import { Document, Page } from "react-pdf"; -// import 'react-pdf/dist/Page/AnnotationLayer.css'; -import { RouteStore } from "../../../server/RouteStore"; +import { KeyCodes } from '../../northstar/utils/KeyCodes'; +import { CompileScript } from '../../util/Scripting'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; -import { FilterBox } from "../search/FilterBox"; -import { Annotation } from './Annotation'; import { PDFViewer } from "../pdf/PDFViewer"; import { positionSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { CompileScript } from '../../util/Scripting'; -import { Flyout, anchorPoints } from '../DocumentDecorations'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { KeyCodes } from '../../northstar/utils/KeyCodes'; -import { Utils } from '../../../Utils'; -import { Id } from '../../../new_fields/FieldSymbols'; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -35,40 +28,33 @@ export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === K export class PDFBox extends DocComponent(PdfDocument) { public static LayoutString() { return FieldView.LayoutString(PDFBox); } + @observable private _flyout: boolean = false; @observable private _alt = false; @observable private _scrollY: number = 0; + @observable private _pdf: Opt; + @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } - @observable private _flyout: boolean = false; - private _mainCont: React.RefObject; + private _mainCont: React.RefObject = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; - private _keyRef: React.RefObject; - private _valueRef: React.RefObject; - private _scriptRef: React.RefObject; + private _keyRef: React.RefObject = React.createRef(); + private _valueRef: React.RefObject = React.createRef(); + private _scriptRef: React.RefObject = React.createRef(); - constructor(props: FieldViewProps) { - super(props); + componentDidMount() { + this.props.setPdfBox && this.props.setPdfBox(this); - this._mainCont = React.createRef(); + const pdfUrl = Cast(this.props.Document.data, PdfField); + if (pdfUrl instanceof PdfField) { + Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); + } this._reactionDisposer = reaction( () => this.props.Document.scrollY, - () => { - if (this._mainCont.current) { - this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }); - } - } + () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }) ); - - this._keyRef = React.createRef(); - this._valueRef = React.createRef(); - this._scriptRef = React.createRef(); - } - - componentDidMount() { - if (this.props.setPdfBox) this.props.setPdfBox(this); } componentWillUnmount() { @@ -78,6 +64,8 @@ export class PDFBox extends DocComponent(PdfDocumen public GetPage() { return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1; } + + @action public BackPage() { let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1; cp = cp - 1; @@ -86,6 +74,8 @@ export class PDFBox extends DocComponent(PdfDocumen this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight); } } + + @action public GotoPage(p: number) { if (p > 0 && p <= NumCast(this.props.Document.numPages)) { this.props.Document.curPage = p; @@ -93,6 +83,7 @@ export class PDFBox extends DocComponent(PdfDocumen } } + @action public ForwardPage() { let cp = this.GetPage() + 1; if (cp <= NumCast(this.props.Document.numPages)) { @@ -101,6 +92,15 @@ export class PDFBox extends DocComponent(PdfDocumen } } + scrollTo = (y: number) => { + this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); + } + + @action + setPanY = (y: number) => { + this.containingCollectionDocument && (this.containingCollectionDocument.panY = y); + } + private newKeyChange = (e: React.ChangeEvent) => { this._keyValue = e.currentTarget.value; } @@ -109,151 +109,110 @@ export class PDFBox extends DocComponent(PdfDocumen this._valueValue = e.currentTarget.value; } - @action private newScriptChange = (e: React.ChangeEvent) => { this._scriptValue = e.currentTarget.value; } private applyFilter = () => { - let scriptText = ""; - if (this._scriptValue.length > 0) { - scriptText = this._scriptValue; - } else if (this._keyValue.length > 0 && this._valueValue.length > 0) { - scriptText = `return this.${this._keyValue} === ${this._valueValue}`; - } - else { - scriptText = "return true"; - } + let scriptText = this._scriptValue.length > 0 ? this._scriptValue : + this._keyValue.length > 0 && this._valueValue.length > 0 ? + `return this.${this._keyValue} === ${this._valueValue}` : "return true"; let script = CompileScript(scriptText, { params: { this: Doc.name } }); - if (script.compiled) { - this.props.Document.filterScript = new ScriptField(script); - } - } - - @action - private toggleFlyout = () => { - this._flyout = !this._flyout; + script.compiled && (this.props.Document.filterScript = new ScriptField(script)); } @action private resetFilters = () => { this._keyValue = this._valueValue = ""; this._scriptValue = "return true"; - if (this._keyRef.current) { - this._keyRef.current.value = ""; - } - if (this._valueRef.current) { - this._valueRef.current.value = ""; - } - if (this._scriptRef.current) { - this._scriptRef.current.value = ""; - } + this._keyRef.current && (this._keyRef.current.value = ""); + this._valueRef.current && (this._valueRef.current.value = ""); + this._scriptRef.current && (this._scriptRef.current.value = ""); this.applyFilter(); } - scrollTo(y: number) { - this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); - } - settingsPanel() { return !this.props.active() ? (null) : - ( -
    e.stopPropagation()}> - -
    -
    - Annotation View Settings -
    -
    - - -
    -
    - -
    -
    - - -
    + (
    e.stopPropagation()}> + +
    +
    + Annotation View Settings +
    +
    + + +
    +
    + +
    +
    + +
    - ); +
    ); } loaded = (nw: number, nh: number, np: number) => { - if (this.props.Document) { - let doc = this.dataDoc; - doc.numPages = np; - if (doc.nativeWidth && doc.nativeHeight) return; - let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); - doc.nativeWidth = nw; - if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect; - else doc.nativeHeight = nh; - let ccv = this.props.ContainingCollectionView; - if (ccv) { - ccv.props.Document.pdfHeight = nh; - } - doc.height = nh * (doc[WidthSym]() / nw); + this.dataDoc.numPages = np; + if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight) { + let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1); + this.dataDoc.nativeWidth = nw; + this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh; + this.containingCollectionDocument && (this.containingCollectionDocument.pdfHeight = nh); + this.dataDoc.height = nh * (this.dataDoc[WidthSym]() / nw); } } @action onScroll = (e: React.UIEvent) => { - - if (e.currentTarget) { - this._scrollY = e.currentTarget.scrollTop; - let ccv = this.props.ContainingCollectionView; - if (ccv) { - ccv.props.Document.panTransformType = "None"; - ccv.props.Document.scrollY = this._scrollY; - } + if (e.currentTarget && this.containingCollectionDocument) { + this.containingCollectionDocument.panTransformType = "None"; + this.containingCollectionDocument.scrollY = this._scrollY = e.currentTarget.scrollTop; } } - @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } + render() { - // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField); - if (!(pdfUrl instanceof PdfField)) return
    {`pdf, ${this.props.Document.data}, not found`}
    ; let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); - return ( + return (!(pdfUrl instanceof PdfField) || !this._pdf ? +
    {`pdf, ${this.props.Document.data}, not found`}
    :
    { - e.stopPropagation(); - }}> - - {/*
    */} + onWheel={(e: React.WheelEvent) => { e.stopPropagation(); }}> + {this.settingsPanel()}
    ); } - } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index a08ff5969..9d68a86b8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -4,17 +4,20 @@ import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; import { PresentationView } from "../presentationview/PresentationView"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; -import { AnnotationTypes, scale, Viewer } from "./PDFViewer"; +import { AnnotationTypes, scale } from "./PDFViewer"; interface IAnnotationProps { anno: Doc; index: number; - parent: Viewer; + ParentIndex: () => number; + fieldExtensionDoc: Doc; + scrollTo?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; } export default class Annotation extends React.Component { @@ -23,10 +26,8 @@ export default class Annotation extends React.Component { let res = annotationDocs.map(a => { let type = NumCast(a.type); switch (type) { - // case AnnotationTypes.Pin: - // return ; case AnnotationTypes.Region: - return ; + return ; default: return
    ; } @@ -41,7 +42,10 @@ interface IRegionAnnotationProps { width: number; height: number; index: number; - parent: Viewer; + ParentIndex: () => number; + fieldExtensionDoc: Doc; + scrollTo?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; document: Doc; } @@ -51,34 +55,18 @@ class RegionAnnotation extends React.Component { private _reactionDisposer?: IReactionDisposer; private _scrollDisposer?: IReactionDisposer; - private _mainCont: React.RefObject; - - constructor(props: IRegionAnnotationProps) { - super(props); - - this._mainCont = React.createRef(); - } + private _mainCont: React.RefObject = React.createRef(); componentDidMount() { this._reactionDisposer = reaction( - () => BoolCast(this.props.document.delete), - () => { - if (BoolCast(this.props.document.delete)) { - if (this._mainCont.current) { - this._mainCont.current.style.display = "none"; - } - } - }, + () => this.props.document.delete, + () => this.props.document.delete && this._mainCont.current && (this._mainCont.current.style.display = "none"), { fireImmediately: true } ); this._scrollDisposer = reaction( - () => this.props.parent.Index, - () => { - if (this.props.parent.Index === this.props.index) { - this.props.parent.scrollTo(this.props.y * scale); - } - } + () => this.props.ParentIndex(), + () => this.props.ParentIndex() === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale) ); } @@ -88,16 +76,15 @@ class RegionAnnotation extends React.Component { } deleteAnnotation = () => { - let annotation = DocListCast(this.props.parent.props.parent.fieldExtensionDoc.annotations); + let annotation = DocListCast(this.props.fieldExtensionDoc.annotations); let group = FieldValue(Cast(this.props.document.group, Doc)); - if (group && annotation.indexOf(group) !== -1) { - let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); - this.props.parent.props.parent.fieldExtensionDoc.annotations = new List(newAnnotations); - } - if (group) { - let groupAnnotations = DocListCast(group.annotations); - groupAnnotations.forEach(anno => anno.delete = true); + if (annotation.indexOf(group) !== -1) { + let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); + this.props.fieldExtensionDoc.annotations = new List(newAnnotations); + } + + DocListCast(group.annotations).forEach(anno => anno.delete = true); } PDFMenu.Instance.fadeOut(true); @@ -105,9 +92,7 @@ class RegionAnnotation extends React.Component { pinToPres = () => { let group = FieldValue(Cast(this.props.document.group, Doc)); - if (group) { - PresentationView.Instance.PinDoc(group); - } + group && PresentationView.Instance.PinDoc(group); } @action @@ -118,7 +103,7 @@ class RegionAnnotation extends React.Component { let context = await Cast(targetDoc.targetContext, Doc); if (context) { DocumentManager.Instance.jumpToDocument(targetDoc, false, false, - ((doc) => this.props.parent.props.parent.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")), + ((doc) => this.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")), undefined, undefined); } } @@ -151,8 +136,8 @@ class RegionAnnotation extends React.Component { left: this.props.x * scale, width: this.props.width * scale, height: this.props.height * scale, - backgroundColor: this.props.parent.Index === this.props.index ? "green" : StrCast(this.props.document.color) - }}>
    + backgroundColor: this.props.ParentIndex() === this.props.index ? "green" : StrCast(this.props.document.color) + }} /> ); } } \ No newline at end of file diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 27c2a8f1a..7b3d5bfae 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -11,36 +11,34 @@ import { handleBackspace } from "../nodes/PDFBox"; export default class PDFMenu extends React.Component { static Instance: PDFMenu; + private _offsetY: number = 0; + private _offsetX: number = 0; + private _mainCont: React.RefObject = React.createRef(); + private _commentCont = React.createRef(); + private _snippetButton: React.RefObject = React.createRef(); + private _dragging: boolean = false; + @observable private _top: number = -300; @observable private _left: number = -300; @observable private _opacity: number = 1; @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; - - - StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; - Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction; - Delete: () => void = emptyFunction; - Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; - AddTag: (key: string, value: string) => boolean = returnFalse; - PinToPres: () => void = emptyFunction; + @observable private _keyValue: string = ""; + @observable private _valueValue: string = ""; + @observable private _added: boolean = false; @observable public Highlighting: boolean = false; @observable public Status: "pdf" | "annotation" | "snippet" | "" = ""; @observable public Pinned: boolean = false; + public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; + public Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction; + public Delete: () => void = emptyFunction; + public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; + public AddTag: (key: string, value: string) => boolean = returnFalse; + public PinToPres: () => void = emptyFunction; public Marquee: { left: number; top: number; width: number; height: number; } | undefined; - private _offsetY: number = 0; - private _offsetX: number = 0; - private _mainCont: React.RefObject = React.createRef(); - private _commentCont = React.createRef(); - private _snippetButton: React.RefObject = React.createRef(); - private _dragging: boolean = false; - @observable private _keyValue: string = ""; - @observable private _valueValue: string = ""; - @observable private _added: boolean = false; - constructor(props: Readonly<{}>) { super(props); @@ -61,12 +59,10 @@ export default class PDFMenu extends React.Component { e.stopPropagation(); e.preventDefault(); - if (this._dragging) { - return; + if (!this._dragging) { + this.StartDrag(e, this._commentCont.current!); + this._dragging = true; } - - this.StartDrag(e, this._commentCont.current!); - this._dragging = true; } pointerUp = (e: PointerEvent) => { @@ -126,9 +122,20 @@ export default class PDFMenu extends React.Component { @action togglePin = (e: React.MouseEvent) => { this.Pinned = !this.Pinned; - if (!this.Pinned) { - this.Highlighting = false; - } + !this.Pinned && (this.Highlighting = false); + } + + dragStart = (e: React.PointerEvent) => { + document.removeEventListener("pointermove", this.dragging); + document.addEventListener("pointermove", this.dragging); + document.removeEventListener("pointerup", this.dragEnd); + document.addEventListener("pointerup", this.dragEnd); + + this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; + this._offsetY = e.nativeEvent.offsetY; + + e.stopPropagation(); + e.preventDefault(); } @action @@ -147,19 +154,6 @@ export default class PDFMenu extends React.Component { e.preventDefault(); } - dragStart = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.dragging); - document.addEventListener("pointermove", this.dragging); - document.removeEventListener("pointerup", this.dragEnd); - document.addEventListener("pointerup", this.dragEnd); - - this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; - this._offsetY = e.nativeEvent.offsetY; - - e.stopPropagation(); - e.preventDefault(); - } - @action highlightClicked = (e: React.MouseEvent) => { if (!this.Pinned) { @@ -193,13 +187,10 @@ export default class PDFMenu extends React.Component { snippetDrag = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); - if (this._dragging) { - return; - } - this._dragging = true; + if (!this._dragging) { + this._dragging = true; - if (this.Marquee) { - this.Snippet(this.Marquee); + this.Marquee && this.Snippet(this.Marquee); } } @@ -226,36 +217,32 @@ export default class PDFMenu extends React.Component { if (this._keyValue.length > 0 && this._valueValue.length > 0) { this._added = this.AddTag(this._keyValue, this._valueValue); - setTimeout( - () => { - runInAction(() => { - this._added = false; - }); - }, 1000 - ); + setTimeout(action(() => this._added = false), 1000); } } render() { - let buttons = this.Status === "pdf" || this.Status === "snippet" ? [ - , - , - this.Status === "snippet" ? : undefined, - - ] : [ - , - , -
    + let buttons = this.Status === "pdf" || this.Status === "snippet" ? + [ + , + , + , + + ] : [ + , + , +
    , - , + , ]; return ( diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 7158aaffa..9bb18c87f 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -23,12 +23,13 @@ .textLayer { user-select: auto; } -.viewer { - // position: absolute; - // top: 0; -} -.pdfViewere-viewer { +.pdfViewer-viewer { pointer-events:inherit; + width: 100%; +} + +.pdfviewer-placeholder { + content: "HELLO WORLD" } .pdfViewer-text { transform: scale(1.5); @@ -44,9 +45,6 @@ } } } -.pdfViewer-viewerCont { - width:100%; -} .page-cont { .textLayer { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6a99cec59..524bb0420 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,167 +1,104 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import * as rp from "request-promise"; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; +import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, Utils } from "../../../Utils"; +import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; -import { DragManager } from "../../util/DragManager"; -import { PDFBox } from "../nodes/PDFBox"; +import { KeyCodes } from "../../northstar/utils/KeyCodes"; +import { CompileResult, CompileScript } from "../../util/Scripting"; +import Annotation from "./Annotation"; import Page from "./Page"; import "./PDFViewer.scss"; import React = require("react"); -import { CompileScript, CompileResult } from "../../util/Scripting"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Annotation from "./Annotation"; -import { KeyCodes } from "../../northstar/utils/KeyCodes"; -import { DocServer } from "../../DocServer"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); export const scale = 2; -interface IPDFViewerProps { - url: string; - loaded: (nw: number, nh: number, np: number) => void; - scrollY: number; - parent: PDFBox; -} - -/** - * Wrapper that loads the PDF and cascades the pdf down - */ -@observer -export class PDFViewer extends React.Component { - @observable _pdf: Opt; - private _mainDiv = React.createRef(); - - @action - componentDidMount() { - Pdfjs.getDocument(this.props.url).promise.then(pdf => runInAction(() => this._pdf = pdf)); - } - - render() { - return ( -
    - {!this._pdf ? (null) : - } -
    - ); - } -} interface IViewerProps { pdf: Pdfjs.PDFDocumentProxy; + url: string; + Document: Doc; + DataDoc?: Doc; + fieldExtensionDoc: Doc; + fieldKey: string; loaded: (nw: number, nh: number, np: number) => void; scrollY: number; - parent: PDFBox; - mainCont: React.RefObject; - url: string; + scrollTo: (y: number) => void; + active: () => boolean; + setPanY?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; } /** * Handles rendering and virtualization of the pdf */ @observer -export class Viewer extends React.Component { - // _visibleElements is the array of JSX elements that gets rendered - @observable.shallow private _visibleElements: JSX.Element[] = []; - // _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder - @observable private _isPage: string[] = []; +export class PDFViewer extends React.Component { + @observable.shallow private _visibleElements: JSX.Element[] = []; // _visibleElements is the array of JSX elements that gets rendered + @observable private _isPage: string[] = [];// _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @observable private _savedAnnotations: Dictionary = new Dictionary(); - @observable private _script: CompileResult | undefined; + @observable private _script: CompileResult | undefined = CompileScript("return true"); @observable private _searching: boolean = false; - - @observable public Index: number = -1; + @observable private Index: number = -1; private _pageBuffer: number = 1; private _annotationLayer: React.RefObject = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; - private _dropDisposer?: DragManager.DragDropDisposer; private _filterReactionDisposer?: IReactionDisposer; - private _viewer: React.RefObject; - private _mainCont: React.RefObject; + private _viewer: React.RefObject = React.createRef(); + private _mainCont: React.RefObject = React.createRef(); private _pdfViewer: any; - // private _textContent: Pdfjs.TextContent[] = []; private _pdfFindController: any; private _searchString: string = ""; private _selectionText: string = ""; - constructor(props: IViewerProps) { - super(props); - - let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField); - this._script = scriptfield ? scriptfield.script : CompileScript("return true"); - this._viewer = React.createRef(); - this._mainCont = React.createRef(); - } - - setSelectionText = (text: string) => { - this._selectionText = text; - } - componentDidUpdate = (prevProps: IViewerProps) => { - if (this.scrollY !== prevProps.scrollY) { - this.renderPages(); - } + this.scrollY !== prevProps.scrollY && this.renderPages(); } @action componentDidMount = () => { this._reactionDisposer = reaction( - - () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], + () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], async () => { await this.initialLoad(); this.renderPages(); }, { fireImmediately: true }); this._annotationReactionDisposer = reaction( - () => { - return this.props.parent && this.props.parent.fieldExtensionDoc && DocListCast(this.props.parent.fieldExtensionDoc.annotations); - }, - (annotations: Doc[]) => { - annotations && annotations.length && this.renderAnnotations(annotations, true); - }, + () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations), + annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), { fireImmediately: true }); - - if (this.props.parent.props.ContainingCollectionView) { - this._filterReactionDisposer = reaction( - () => this.props.parent.Document.filterScript, - () => { - runInAction(() => { - let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField); - this._script = scriptfield ? scriptfield.script : CompileScript("return true"); - if (this.props.parent.props.ContainingCollectionView) { - let fieldDoc = Doc.resolvedFieldDataDoc(this.props.parent.props.ContainingCollectionView.props.DataDoc ? - this.props.parent.props.ContainingCollectionView.props.DataDoc : this.props.parent.props.ContainingCollectionView.props.Document, this.props.parent.props.ContainingCollectionView.props.fieldKey, "true"); - let ccvAnnos = DocListCast(fieldDoc.annotations); - ccvAnnos.forEach(d => { - if (this._script && this._script.compiled) { - let run = this._script.run(d); - if (run.success) { - d.opacity = run.result ? 1 : 0; - } - } - }); + this._filterReactionDisposer = reaction( + () => this.props.Document.filterScript, + action(() => { + let scriptfield = Cast(this.props.Document.filterScript, ScriptField); + this._script = scriptfield ? scriptfield.script : CompileScript("return true"); + DocListCast(this.props.fieldExtensionDoc.annotations).forEach(d => { + if (this._script && this._script.compiled) { + let run = this._script.run(d); + if (run.success) { + d.opacity = run.result ? 1 : 0; } - this.Index = -1; - }); - } - ); - } - - if (this._mainCont.current) { - this._dropDisposer = this._mainCont.current && DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); - } + } + }); + this.Index = -1; + }), + { fireImmediately: true } + ); document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); @@ -171,89 +108,50 @@ export class Viewer extends React.Component { this._reactionDisposer && this._reactionDisposer(); this._annotationReactionDisposer && this._annotationReactionDisposer(); this._filterReactionDisposer && this._filterReactionDisposer(); - this._dropDisposer && this._dropDisposer(); document.removeEventListener("copy", this.copy); } private copy = (e: ClipboardEvent) => { - if (this.props.parent.props.active()) { + if (this.props.active() && e.clipboardData) { let text = this._selectionText; - if (e.clipboardData) { - e.clipboardData.setData("text/plain", text); - e.clipboardData.setData("dash/pdfOrigin", this.props.parent.props.Document[Id]); - let annoDoc = this.makeAnnotationDocument(undefined, 0, "#0390fc"); - e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); - e.preventDefault(); - } + let annoDoc = this.makeAnnotationDocument(undefined, 0, "#0390fc"); + e.clipboardData.setData("text/plain", text); + e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); + e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); + e.preventDefault(); } - // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); - // if (targetAnnotations) { - // targetAnnotations.push(destDoc); - // } } paste = (e: ClipboardEvent) => { - if (e.clipboardData) { - if (e.clipboardData.getData("dash/pdfOrigin") === this.props.parent.props.Document[Id]) { - let linkDocId = e.clipboardData.getData("dash/linkDoc"); - if (linkDocId) { - DocServer.GetRefField(linkDocId).then(async (link) => { - if (!(link instanceof Doc)) { - return; - } - let proto = Doc.GetProto(link); - let source = await Cast(proto.anchor1, Doc); - proto.anchor2 = this.makeAnnotationDocument(source, 0, "#0390fc", false); - }); - } - } + if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) { + let linkDocId = e.clipboardData.getData("dash/linkDoc"); + linkDocId && DocServer.GetRefField(linkDocId).then(async (link) => + (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), 0, "#0390fc", false))); } } - scrollTo(y: number) { - if (this.props.mainCont.current) { - this.props.parent.scrollTo(y); - } + setSelectionText = (text: string) => { + this._selectionText = text; } @action initialLoad = async () => { if (this._pageSizes.length === 0) { - let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); this._isPage = Array(this.props.pdf.numPages); - // this._textContent = Array(this.props.pdf.numPages); - const proms: Pdfjs.PDFPromise[] = []; - for (let i = 0; i < this.props.pdf.numPages; i++) { - proms.push(this.props.pdf.getPage(i + 1).then(page => runInAction(() => { - pageSizes[i] = { + this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); + await Promise.all(this._pageSizes.map>((val, i) => + this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => { + this._pageSizes.splice(i, 1, { width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale, height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale - }; - // let x = page.getViewport(scale); - // page.getTextContent().then((text: Pdfjs.TextContent) => { - // // let tc = new Pdfjs.TextContentItem() - // // let tc = {str: } - // this._textContent[i] = text; - // // text.items.forEach(t => { - // // tcStr += t.str; - // // }) - // }); - // pageSizes[i] = { width: x.width, height: x.height }; - }))); - } - await Promise.all(proms); - runInAction(() => - Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage)); - this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages); - // this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages); - - let startY = NumCast(this.props.parent.Document.startY); - let ccv = this.props.parent.props.ContainingCollectionView; - if (ccv) { - ccv.props.Document.panY = startY; - } - this.props.parent.Document.scrollY = 0; - this.props.parent.Document.scrollY = startY + 1; + }); + this.getPlaceholderPage(i); + })))); + this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); + + let startY = NumCast(this.props.Document.startY); + this.props.setPanY && this.props.setPanY(startY); + this.props.Document.scrollY = startY + 1; } } @@ -262,8 +160,8 @@ export class Viewer extends React.Component { let annoDocs: Doc[] = []; let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); - mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title); - mainAnnoDoc.pdfDoc = this.props.parent.props.Document; + mainAnnoDoc.title = "Annotation on " + StrCast(this.props.Document.title); + mainAnnoDoc.pdfDoc = this.props.Document; let minY = Number.MAX_VALUE; this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { @@ -288,34 +186,19 @@ export class Viewer extends React.Component { mainAnnoDoc.y = Math.max(minY, 0); mainAnnoDoc.annotations = new List(annoDocs); if (sourceDoc && createLink) { - DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); + DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title)); } this._savedAnnotations.clear(); this.Index = -1; return mainAnnoDoc; } - drop = async (e: Event, de: DragManager.DropEvent) => { - // if (de.data instanceof DragManager.LinkDragData) { - // let sourceDoc = de.data.linkSourceDocument; - // let destDoc = this.makeAnnotationDocument(sourceDoc, 1, "red"); - // de.data.droppedDocuments.push(destDoc); - // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); - // if (targetAnnotations) { - // targetAnnotations.push(destDoc); - // } - // else { - // this.props.parent.fieldExtensionDoc.annotations = new List([destDoc]); - // } - // e.stopPropagation(); - // } - } /** * Called by the Page class when it gets rendered, initializes the lists and * puts a placeholder with all of the correct page sizes when all of the pages have been loaded. */ @action - pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { + pageLoaded = (page: Pdfjs.PDFPageViewport): void => { this.props.loaded(page.width, page.height, this.props.pdf.numPages); } @@ -325,8 +208,9 @@ export class Viewer extends React.Component { this._isPage[page] = "none"; this._visibleElements[page] = (
    - ); + style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }}> + "PAGE IS LOADING... " +
    ); } } @@ -344,8 +228,8 @@ export class Viewer extends React.Component { key={`${this.props.url}-rendered-${page + 1}`} name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`} pageLoaded={this.pageLoaded} - parent={this.props.parent} - makePin={emptyFunction} + fieldExtensionDoc={this.props.fieldExtensionDoc} + Document={this.props.Document} renderAnnotations={this.renderAnnotations} createAnnotation={this.createAnnotation} sendAnnotations={this.receiveAnnotations} @@ -363,9 +247,8 @@ export class Viewer extends React.Component { let handleError = () => this.getRenderedPage(page); if (this._isPage[page] !== "image") { this._isPage[page] = "image"; - const address = this.props.url; try { - let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`))); + let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${page + 1}.PNG`))); runInAction(() => this._visibleElements[page] = ); @@ -382,22 +265,15 @@ export class Viewer extends React.Component { // endIndex: where to end rendering pages @computed get endIndex(): number { - return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + this._pageSizes[0].height) + this._pageBuffer); + return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer); } @action renderPages = () => { - for (let i = 0; i < this.props.pdf.numPages; i++) { - if (i < this.startIndex || i > this.endIndex) { - this.getPlaceholderPage(i); // pages outside of the pdf use empty stand-in divs - } else { - if (this.props.parent.props.active()) { - this.getRenderedPage(i); - } else { - this.getPageImage(i); - } - } - } + Array.from(Array(this.props.pdf.numPages).keys()).filter(p => this._isPage[p] !== undefined).map(i => + (i < this.startIndex || i > this.endIndex) ? this.getPlaceholderPage(i) : // pages outside of the pdf use empty stand-in divs + this.props.active() ? this.getRenderedPage(i) : this.getPageImage(i) + ); } @action @@ -430,7 +306,7 @@ export class Viewer extends React.Component { getPageFromScroll = (vOffset: number) => { let index = 0; let currOffset = vOffset; - while (index < this._pageSizes.length && currOffset - this._pageSizes[index].height > 0) { + while (index < this._pageSizes.length && this._pageSizes[index] && currOffset - this._pageSizes[index].height > 0) { currOffset -= this._pageSizes[index++].height; } return index; @@ -461,52 +337,39 @@ export class Viewer extends React.Component { } } - renderAnnotation = (anno: Doc, index: number): JSX.Element => { - return ; - } - - @action - pointerDown = () => { - // this._searching = false; - } + getIndex = () => this.Index; @action search = (searchString: string) => { if (this._pdfViewer._pageViewsReady) { - this._pdfFindController.executeCommand('find', - { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }); } else { - let container = this._mainCont.current; - if (container) { - container.addEventListener("pagesloaded", () => { - console.log("rendered"); - this._pdfFindController.executeCommand('find', - { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); - }); - container.addEventListener("pagerendered", () => { - console.log("rendered"); - this._pdfFindController.executeCommand('find', - { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); - }); + if (this._mainCont.current) { + this._mainCont.current.addEventListener("pagesloaded", () => + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }) + ); + this._mainCont.current.addEventListener("pagerendered", () => + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }) + ); } } @@ -575,15 +438,10 @@ export class Viewer extends React.Component { linkService: simpleLinkService }); simpleLinkService.setPdf(this.props.pdf); - container.addEventListener("pagesinit", () => { - this._pdfViewer.currentScaleValue = 1; - }); - container.addEventListener("pagerendered", () => { - console.log("rendered"); - }); + container.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1); + container.addEventListener("pagerendered", () => console.log("rendered")); this._pdfViewer.setDocument(this.props.pdf); this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer); - // this._pdfFindController._linkService = pdfLinkService; this._pdfViewer.findController = this._pdfFindController; } } @@ -602,10 +460,6 @@ export class Viewer extends React.Component { @action prevAnnotation = (e: React.MouseEvent) => { e.stopPropagation(); - - // if (this.Index > 0) { - // this.Index--; - // } this.Index = Math.max(this.Index - 1, 0); } @@ -626,49 +480,27 @@ export class Viewer extends React.Component { this.Index = Math.min(this.Index + 1, filtered.length - 1); } - nextResult = () => { - // if (this._viewer.current) { - // let results = this._pdfFindController.pageMatches; - // if (results && results.length) { - // if (this._pageIndex === this.props.pdf.numPages && this._matchIndex === results[this._pageIndex].length - 1) { - // return; - // } - // if (this._pageIndex === -1 || this._matchIndex === results[this._pageIndex].length - 1) { - // this._matchIndex = 0; - // this._pageIndex++; - // } - // else { - // this._matchIndex++; - // } - // this._pdfFindController._nextMatch() - // let nextMatch = this._viewer.current.children[this._pageIndex].children[1].children[results[this._pageIndex][this._matchIndex]]; - // rconsole.log(nextMatch); - // this.props.parent.scrollTo(nextMatch.getBoundingClientRect().top); - // nextMatch.setAttribute("style", nextMatch.getAttribute("style") ? nextMatch.getAttribute("style") + ", background-color: green" : "background-color: green"); - // } - // } - } - render() { - let compiled = this._script; return ( -
    +
    {this._visibleElements}
    -
    +
    {this._annotations.filter(anno => { - if (compiled && compiled.compiled) { - let run = compiled.run({ this: anno }); + if (this._script && this._script.compiled) { + let run = this._script.run({ this: anno }); if (run.success) { return run.result; } } return true; }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y)) - .map((anno: Doc, index: number) => this.renderAnnotation(anno, index))} + .map((anno: Doc, index: number) => + + )}
    e.stopPropagation()} @@ -683,19 +515,19 @@ export class Viewer extends React.Component {
    - {/* - */} - e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} /> + e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" onChange={this.searchStringChanged} />
    +
    + + - e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" onChange={this.searchStringChanged} /> - + + - - -
    - ); + +
    ); } } -export enum AnnotationTypes { - Region -} +export enum AnnotationTypes { Region } class SimpleLinkService { externalLinkTarget: any = null; diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss index 6467c4979..af1628a6f 100644 --- a/src/client/views/pdf/Page.scss +++ b/src/client/views/pdf/Page.scss @@ -1,21 +1,21 @@ .pdfPage-cont { + position: relative; + + .pdfPage-canvasContainer { + position: absolute; + } .pdfPage-dragAnnotationBox { position: absolute; background-color: transparent; opacity: 0.1; } - .pdfPage-annotationLayer { - pointer-events: none; + + .pdfPage-textLayer { + position: absolute; width: 100%; height: 100%; - position: relative; - top: -100%; - } - - .PdfPage-textLayer { - position: relative; div { user-select: text; } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 47f2e7b56..fd4fbfb21 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -49,30 +49,21 @@ export default class Page extends React.Component { private _startY: number = 0; private _startX: number = 0; - componentDidMount = (): void => { - this.loadPage(this.props.pdf); - } + componentDidMount = (): void => this.loadPage(this.props.pdf); - componentDidUpdate = (): void => { - this.loadPage(this.props.pdf); - } + componentDidUpdate = (): void => this.loadPage(this.props.pdf); - componentWillUnmount = (): void => { - this._reactionDisposer && this._reactionDisposer(); - } + componentWillUnmount = (): void => this._reactionDisposer && this._reactionDisposer(); - private loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => { - this._state !== "rendering" && !this._page && pdf.getPage(this._currPage).then( - (page: Pdfjs.PDFPageProxy): void => { - this._state = "rendering"; - this.renderPage(page); - }); + loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => { + pdf.getPage(this._currPage).then(page => this.renderPage(page)); } @action - private renderPage = (page: Pdfjs.PDFPageProxy): void => { + renderPage = (page: Pdfjs.PDFPageProxy): void => { // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes - if (this._canvas.current && this._textLayer.current) { + if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) { + this._state = "rendering"; let viewport = page.getViewport(scale); this._canvas.current.width = this._width = viewport.width; this._canvas.current.height = this._height = viewport.height; @@ -113,7 +104,7 @@ export default class Page extends React.Component { if (this._textLayer.current) { let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); targetDoc.targetPage = this.props.page; - let annotationDoc = this.highlight(undefined, "pink"); + let annotationDoc = this.highlight(undefined, "red"); annotationDoc.linkedToDoc = false; let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { @@ -293,23 +284,16 @@ export default class Page extends React.Component { render() { return (
    -
    - -
    -
    -
    -
    + +
    -
    +
    ); } -} +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 913055f1b6f8eac88eda2ba08715c9cc3b013f08 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 6 Aug 2019 23:54:39 -0400 Subject: pdf tweaks. --- src/client/views/pdf/Annotation.tsx | 39 +++++++++++++------------------------ src/client/views/pdf/Page.tsx | 8 ++------ 2 files changed, 15 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 947f5a2e8..2610f6c6e 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -9,7 +9,7 @@ import { DocumentManager } from "../../util/DocumentManager"; import { PresentationView } from "../presentationview/PresentationView"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; -import { AnnotationTypes, scale } from "./PDFViewer"; +import { scale } from "./PDFViewer"; interface IAnnotationProps { anno: Doc; @@ -22,17 +22,8 @@ interface IAnnotationProps { export default class Annotation extends React.Component { render() { - let annotationDocs = DocListCast(this.props.anno.annotations); - let res = annotationDocs.map(a => { - let type = NumCast(a.type); - switch (type) { - case AnnotationTypes.Region: - return ; - default: - return
    ; - } - }); - return res; + return DocListCast(this.props.anno.annotations).map(a => ( + )); } } @@ -51,8 +42,6 @@ interface IRegionAnnotationProps { @observer class RegionAnnotation extends React.Component { - @observable private _backgroundColor: string = "red"; - private _reactionDisposer?: IReactionDisposer; private _scrollDisposer?: IReactionDisposer; private _mainCont: React.RefObject = React.createRef(); @@ -60,13 +49,13 @@ class RegionAnnotation extends React.Component { componentDidMount() { this._reactionDisposer = reaction( () => this.props.document.delete, - () => this.props.document.delete && this._mainCont.current && (this._mainCont.current.style.display = "none"), + (del) => del && this._mainCont.current && (this._mainCont.current.style.display = "none"), { fireImmediately: true } ); this._scrollDisposer = reaction( () => this.props.ParentIndex(), - () => this.props.ParentIndex() === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale) + (ind) => ind === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale) ); } @@ -129,15 +118,13 @@ class RegionAnnotation extends React.Component { } render() { - return ( -
    - ); + return (
    ); } } \ No newline at end of file diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index fd4fbfb21..a1f807f13 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -198,16 +198,13 @@ export default class Page extends React.Component { onSelectEnd = (e: PointerEvent): void => { if (this._marqueeing) { this._marqueeing = false; - if (this._marquee.current) { + if (this._marquee.current) { // make a copy of the marquee let copy = document.createElement("div"); - // make a copy of the marquee let style = this._marquee.current.style; copy.style.left = style.left; copy.style.top = style.top; copy.style.width = style.width; copy.style.height = style.height; - - // apply the appropriate background, opacity, and transform copy.style.border = style.border; copy.style.opacity = style.opacity; copy.className = "pdfPage-annotationBox"; @@ -293,7 +290,6 @@ export default class Page extends React.Component { }}>
    -
    - ); +
    ); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 4d3962cf221cb2421835c7016562433077e7b200 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 7 Aug 2019 01:48:15 -0400 Subject: pivot view script retrieval and context menu behavior improvements --- src/client/views/collections/CollectionView.tsx | 14 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 12 ++--- src/scraping/buxton/source/Silverlight.dmg | Bin 0 -> 15097394 bytes src/server/index.ts | 52 ++++++++++++--------- 4 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 src/scraping/buxton/source/Silverlight.dmg (limited to 'src') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 59572b7e7..60ede17e7 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEye } from '@fortawesome/free-regular-svg-icons'; -import { faColumns, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons'; +import { faColumns, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree, faCopy } from '@fortawesome/free-solid-svg-icons'; import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; import * as React from 'react'; @@ -20,7 +20,7 @@ import { CollectionTreeView } from "./CollectionTreeView"; import { CollectionViewBaseChrome } from './CollectionViewChromes'; export const COLLECTION_BORDER_WIDTH = 2; -library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any); +library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); @observer export class CollectionView extends React.Component { @@ -86,7 +86,13 @@ export class CollectionView extends React.Component { onContextMenu = (e: React.MouseEvent): void => { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let subItems: ContextMenuProps[] = []; - subItems.push({ description: "Freeform", event: () => this.props.Document.viewType = CollectionViewType.Freeform, icon: "signature" }); + subItems.push({ + description: "Freeform", event: () => { + this.props.Document.viewType = CollectionViewType.Freeform; + delete this.props.Document.arrangeInit; + delete this.props.Document.arrangeScript; + }, icon: "signature" + }); if (CollectionBaseView.InSafeMode()) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } @@ -97,7 +103,7 @@ export class CollectionView extends React.Component { switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) }); - subItems.push({ description: "Pivot", icon: "fingerprint", event: () => CollectionFreeFormView.SetPivotLayout(this.props.Document) }); + subItems.push({ description: "Pivot", icon: "copy", event: () => CollectionFreeFormView.SetPivotLayout(this.props.Document) }); break; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 17c4e83b0..110ac2f25 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -51,14 +51,10 @@ export const panZoomSchema = createSchema({ export namespace PivotView { - export let arrangeInit: string; - export let arrangeScript: string; + export let scripts: { arrangeInit: string, arrangeScript: string }; export async function loadLayouts() { - let response = await fetch(Utils.prepend("/layoutscripts")); - let scripts = JSON.parse(await response.text()); - arrangeInit = scripts[0]; - arrangeScript = scripts[1]; + scripts = JSON.parse(await (await fetch(Utils.prepend("/layoutscripts"))).text()); } } @@ -815,8 +811,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } target[key] = new ScriptField(script); }; - setSpecifiedLayoutField(PivotView.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined); - setSpecifiedLayoutField(PivotView.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); + setSpecifiedLayoutField(PivotView.scripts.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined); + setSpecifiedLayoutField(PivotView.scripts.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); } render() { diff --git a/src/scraping/buxton/source/Silverlight.dmg b/src/scraping/buxton/source/Silverlight.dmg new file mode 100644 index 000000000..d88290362 Binary files /dev/null and b/src/scraping/buxton/source/Silverlight.dmg differ diff --git a/src/server/index.ts b/src/server/index.ts index d735a6d8c..a0101e3f8 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,6 @@ require('dotenv').config(); import * as bodyParser from 'body-parser'; -import { exec } from 'child_process'; +import { exec, ExecOptions } from 'child_process'; import * as cookieParser from 'cookie-parser'; import * as express from 'express'; import * as session from 'express-session'; @@ -149,31 +149,39 @@ app.get("/pull", (req, res) => })); app.get("/buxton", (req, res) => { - let buxton_scraping = path.join(__dirname, '../scraping/buxton'); - exec('python scraper.py', { cwd: buxton_scraping }, (err, stdout, sterr) => { - if (err) { - res.send(err.message); - return; + let cwd = '../scraping/buxton'; + + let onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); }; + let onRejected = (err: any) => { console.error(err.message); res.send(err); }; + let tryPython3 = () => command_line('python3 scraper.py', cwd).then(onResolved, onRejected); + + command_line('python scraper.py', cwd).then(onResolved, tryPython3); +}); + +const command_line = (command: string, fromDirectory?: string) => { + return new Promise((resolve, reject) => { + let options: ExecOptions = {}; + if (fromDirectory) { + options.cwd = path.join(__dirname, fromDirectory); } - console.log(stdout); - res.redirect("/"); + exec(command, options, (err, stdout) => err ? reject(err) : resolve(stdout)); }); -}); +}; + +const read_text_file = (relativePath: string) => { + let target = path.join(__dirname, relativePath); + return new Promise((resolve, reject) => { + fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString())); + }); +}; app.get('/layoutscripts', (req, res) => { - let scripts: string[] = []; - let handler = (err: NodeJS.ErrnoException | null, data: Buffer) => { - if (err) { - console.log(err.message); - return; - } - scripts.push(data.toString()); - if (scripts.length === 2) { - res.send(JSON.stringify(scripts)); - } - }; - fs.readFile(path.join(__dirname, '../scraping/buxton/scripts/initialization.txt'), handler); - fs.readFile(path.join(__dirname, '../scraping/buxton/scripts/layout.txt'), handler); + let prefix = '../scraping/buxton/scripts/'; + read_text_file(prefix + 'initialization.txt').then(arrangeInit => { + read_text_file(prefix + 'layout.txt').then(arrangeScript => { + res.send(JSON.stringify({ arrangeInit, arrangeScript })); + }); + }); }); app.get("/version", (req, res) => { -- cgit v1.2.3-70-g09d2 From 74cb792cb1ef9fd11b7bd635fb25518903757204 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 7 Aug 2019 01:49:30 -0400 Subject: removed dmg --- src/scraping/buxton/source/Silverlight.dmg | Bin 15097394 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/scraping/buxton/source/Silverlight.dmg (limited to 'src') diff --git a/src/scraping/buxton/source/Silverlight.dmg b/src/scraping/buxton/source/Silverlight.dmg deleted file mode 100644 index d88290362..000000000 Binary files a/src/scraping/buxton/source/Silverlight.dmg and /dev/null differ -- cgit v1.2.3-70-g09d2 From d906398528e6ab35bb1b8d9c36de61027380afd9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 7 Aug 2019 08:01:00 -0400 Subject: fixed typo. moved fields to annotation data doc --- src/client/views/pdf/PDFViewer.tsx | 26 +++++++++++++------------- src/client/views/pdf/Page.tsx | 1 - 2 files changed, 13 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 411857223..7c445c373 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -187,12 +187,14 @@ export class PDFViewer extends React.Component { (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); })); - mainAnnoDoc.title = "Annotation on " + StrCast(this.props.Document.title); - mainAnnoDoc.pdfDoc = this.props.Document; - mainAnnoDoc.y = Math.max(minY, 0); - mainAnnoDoc.annotations = new List(annoDocs); + let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); + mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); + mainAnnoDocProto.pdfDoc = this.props.Document; + mainAnnoDocProto.annotationOn = this.props.Document; + mainAnnoDocProto.y = Math.max(minY, 0); + mainAnnoDocProto.annotations = new List(annoDocs); if (sourceDoc && createLink) { - DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title)); + DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title)); } this._savedAnnotations.clear(); this.Index = -1; @@ -433,30 +435,28 @@ export class PDFViewer extends React.Component { )}
    e.stopPropagation()} - style={{ nbottom: -this.props.scrollY, left: `${this._searching ? 0 : 100}%` }}> + style={{ bottom: -this.props.scrollY, left: `${this._searching ? 0 : 100}%` }}> +
    ); } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index a1f807f13..a15bed255 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -88,7 +88,6 @@ export default class Page extends React.Component { highlight = (targetDoc: Doc | undefined, color: string) => { // creates annotation documents for current highlights let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, scale, color, false); - Doc.GetProto(annotationDoc).annotationOn = this.props.Document; Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc); return annotationDoc; } -- cgit v1.2.3-70-g09d2 From adb91b035bd18ff407ce0b2decc07c779282c008 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 7 Aug 2019 12:40:45 -0400 Subject: added zooming to pdfs --- src/client/views/Main.tsx | 5 ++ .../collectionFreeForm/CollectionFreeFormView.tsx | 63 ++++++++++------------ src/client/views/nodes/PDFBox.scss | 3 ++ src/client/views/nodes/PDFBox.tsx | 59 ++++++++------------ src/client/views/pdf/PDFViewer.tsx | 2 +- src/client/views/pdf/Page.tsx | 29 +++++----- 6 files changed, 75 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 1cf13aa74..5fd42c0df 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -39,5 +39,10 @@ let swapDocs = async () => { (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled"; CurrentUserUtils.UserDocument.chromeStatus = "disabled"; await swapDocs(); + document.getElementById('root')!.addEventListener('wheel', event => { + if (event.ctrlKey) { + event.preventDefault() + } + }, true); ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a08a12426..d347e02b6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -352,12 +352,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { if (BoolCast(this.props.Document.lockedPosition)) return; - // if (!this.props.active()) { - // return; - // } - if (this.props.Document.type === "pdf") { + if (!e.ctrlKey && this.props.Document.scrollY !== undefined) { + e.stopPropagation(); return; } + let childSelected = this.childDocs.some(doc => { var dv = DocumentManager.Instance.getDocumentView(doc); return dv && SelectionManager.IsSelected(dv) ? true : false; @@ -366,21 +365,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return; } e.stopPropagation(); - const coefficient = 1000; - - if (e.ctrlKey) { - let deltaScale = (1 - (e.deltaY / coefficient)); - let nw = this.nativeWidth * deltaScale; - let nh = this.nativeHeight * deltaScale; - if (nw && nh) { - this.props.Document.nativeWidth = nw; - this.props.Document.nativeHeight = nh; - } - e.stopPropagation(); - e.preventDefault(); - } else { - // if (modes[e.deltaMode] === 'pixels') coefficient = 50; - // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height?? + + // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix. + // if (e.ctrlKey && this.props.Document.scrollY === undefined) { + // let deltaScale = (1 - (e.deltaY / coefficient)); + // let nw = this.nativeWidth * deltaScale; + // let nh = this.nativeHeight * deltaScale; + // if (nw && nh) { + // this.props.Document.nativeWidth = nw; + // this.props.Document.nativeHeight = nh; + // } + // e.preventDefault(); + // } + // else + { let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); @@ -392,21 +390,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); this.props.Document.scale = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); - e.stopPropagation(); + e.preventDefault(); } } @action setPan(panX: number, panY: number) { - if (BoolCast(this.props.Document.lockedPosition)) return; - this.props.Document.panTransformType = "None"; - var scale = this.getLocalTransform().inverse().Scale; - const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); - const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); - this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; - this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY; - if (this.props.Document.scrollY) { - this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym](); + if (!BoolCast(this.props.Document.lockedPosition)) { + this.props.Document.panTransformType = "None"; + var scale = this.getLocalTransform().inverse().Scale; + const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); + const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); + this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; + this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; + if (this.props.Document.scrollHeight !== undefined) this.props.Document.scrollY = this.isAnnotationOverlay ? newPanY : panY; + else this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; } } @@ -490,12 +488,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.Document.scale = scale; } - getScale = () => { - if (this.Document.scale) { - return this.Document.scale; - } - return 1; - } + getScale = () => this.Document.scale ? this.Document.scale : 1; getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps { diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index a1bab0409..c88a94c28 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -5,6 +5,9 @@ height: 100%; overflow-y: scroll; overflow-x: hidden; + .pdfBox-scrollHack { + pointer-events: none; + } } .pdfBox-cont { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 56e720bf7..2759b46d4 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -30,10 +30,11 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _flyout: boolean = false; @observable private _alt = false; - @observable private _scrollY: number = 0; @observable private _pdf: Opt; + @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } + @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } private _mainCont: React.RefObject = React.createRef(); private _reactionDisposer?: IReactionDisposer; @@ -62,16 +63,16 @@ export class PDFBox extends DocComponent(PdfDocumen } public GetPage() { - return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1; + return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.nativeHeight)) + 1; } @action public BackPage() { - let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1; + let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.nativeHeight)) + 1; cp = cp - 1; if (cp > 0) { this.props.Document.curPage = cp; - this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight); + this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); } } @@ -79,7 +80,7 @@ export class PDFBox extends DocComponent(PdfDocumen public GotoPage(p: number) { if (p > 0 && p <= NumCast(this.props.Document.numPages)) { this.props.Document.curPage = p; - this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.pdfHeight); + this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.nativeHeight); } } @@ -88,31 +89,16 @@ export class PDFBox extends DocComponent(PdfDocumen let cp = this.GetPage() + 1; if (cp <= NumCast(this.props.Document.numPages)) { this.props.Document.curPage = cp; - this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight); + this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); } } - scrollTo = (y: number) => { - this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); - } - @action setPanY = (y: number) => { this.containingCollectionDocument && (this.containingCollectionDocument.panY = y); } - private newKeyChange = (e: React.ChangeEvent) => { - this._keyValue = e.currentTarget.value; - } - - private newValueChange = (e: React.ChangeEvent) => { - this._valueValue = e.currentTarget.value; - } - - private newScriptChange = (e: React.ChangeEvent) => { - this._scriptValue = e.currentTarget.value; - } - + @action private applyFilter = () => { let scriptText = this._scriptValue.length > 0 ? this._scriptValue : this._keyValue.length > 0 && this._valueValue.length > 0 ? @@ -121,7 +107,10 @@ export class PDFBox extends DocComponent(PdfDocumen script.compiled && (this.props.Document.filterScript = new ScriptField(script)); } - @action + scrollTo = (y: number) => { + this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); + } + private resetFilters = () => { this._keyValue = this._valueValue = ""; this._scriptValue = "return true"; @@ -130,6 +119,9 @@ export class PDFBox extends DocComponent(PdfDocumen this._scriptRef.current && (this._scriptRef.current.value = ""); this.applyFilter(); } + private newKeyChange = (e: React.ChangeEvent) => this._keyValue = e.currentTarget.value; + private newValueChange = (e: React.ChangeEvent) => this._valueValue = e.currentTarget.value; + private newScriptChange = (e: React.ChangeEvent) => this._scriptValue = e.currentTarget.value; settingsPanel() { return !this.props.active() ? (null) : @@ -176,12 +168,12 @@ export class PDFBox extends DocComponent(PdfDocumen loaded = (nw: number, nh: number, np: number) => { this.dataDoc.numPages = np; - if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight) { + if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight || !this.dataDoc.scrollHeight) { let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1); this.dataDoc.nativeWidth = nw; this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh; - this.containingCollectionDocument && (this.containingCollectionDocument.pdfHeight = nh); - this.dataDoc.height = nh * (this.dataDoc[WidthSym]() / nw); + this.dataDoc.height = this.dataDoc[WidthSym]() * (nh / nw); + this.dataDoc.scrollHeight = np * this.dataDoc.nativeHeight; } } @@ -189,14 +181,10 @@ export class PDFBox extends DocComponent(PdfDocumen onScroll = (e: React.UIEvent) => { if (e.currentTarget && this.containingCollectionDocument) { this.containingCollectionDocument.panTransformType = "None"; - this.containingCollectionDocument.scrollY = this._scrollY = e.currentTarget.scrollTop; + this.containingCollectionDocument.scrollY = e.currentTarget.scrollTop; } } - @computed get fieldExtensionDoc() { - return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); - } - render() { const pdfUrl = Cast(this.props.Document.data, PdfField); let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); @@ -205,14 +193,13 @@ export class PDFBox extends DocComponent(PdfDocumen
    { e.stopPropagation(); }}> - +
    + {this.settingsPanel()} -
    - ); +
    ); } } \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7c445c373..021c04723 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -160,7 +160,7 @@ export class PDFViewer extends React.Component { })))); this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); - let startY = NumCast(this.props.Document.startY); + let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.scrollY)); this.props.setPanY && this.props.setPanY(startY); this.props.Document.scrollY = startY + 1; } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index a15bed255..6de2db427 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -143,6 +143,7 @@ export default class Page extends React.Component { @action onPointerDown = (e: React.PointerEvent): void => { // if alt+left click, drag and annotate + if (this.props.Document.scale !== 1) return; if (e.altKey && e.button === 0) { e.stopPropagation(); } @@ -197,21 +198,21 @@ export default class Page extends React.Component { onSelectEnd = (e: PointerEvent): void => { if (this._marqueeing) { this._marqueeing = false; - if (this._marquee.current) { // make a copy of the marquee - let copy = document.createElement("div"); - let style = this._marquee.current.style; - copy.style.left = style.left; - copy.style.top = style.top; - copy.style.width = style.width; - copy.style.height = style.height; - copy.style.border = style.border; - copy.style.opacity = style.opacity; - copy.className = "pdfPage-annotationBox"; - this.props.createAnnotation(copy, this.props.page); - this._marquee.current.style.opacity = "0"; - } - if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { + if (this._marquee.current) { // make a copy of the marquee + let copy = document.createElement("div"); + let style = this._marquee.current.style; + copy.style.left = style.left; + copy.style.top = style.top; + copy.style.width = style.width; + copy.style.height = style.height; + copy.style.border = style.border; + copy.style.opacity = style.opacity; + copy.className = "pdfPage-annotationBox"; + this.props.createAnnotation(copy, this.props.page); + this._marquee.current.style.opacity = "0"; + } + if (!e.ctrlKey) { PDFMenu.Instance.Status = "snippet"; PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight }; -- cgit v1.2.3-70-g09d2 From 221acd0cfb4831435d1d1b61b86c2cc5e3d3b413 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 7 Aug 2019 12:50:58 -0400 Subject: got rid of scrollY from pdfs! --- src/client/views/collections/CollectionPDFView.tsx | 16 +--------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 ++---- src/client/views/nodes/PDFBox.tsx | 18 +++++++++--------- src/client/views/pdf/PDFViewer.tsx | 21 ++++++++++----------- 4 files changed, 22 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 3736ebada..8eda4d9ee 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,7 +1,6 @@ -import { action, IReactionDisposer, observable, reaction, computed } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { Id } from "../../../new_fields/FieldSymbols"; -import { NumCast } from "../../../new_fields/Types"; import { emptyFunction } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; @@ -19,21 +18,8 @@ export class CollectionPDFView extends React.Component { } private _pdfBox?: PDFBox; - private _reactionDisposer?: IReactionDisposer; private _buttonTray: React.RefObject = React.createRef(); - componentDidMount() { - this._reactionDisposer = reaction( - () => NumCast(this.props.Document.scrollY), - () => this.props.Document.panY = NumCast(this.props.Document.scrollY), - { fireImmediately: true } - ); - } - - componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - } - @computed get uIButtons() { return ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d347e02b6..8322625f1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -352,7 +352,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { if (BoolCast(this.props.Document.lockedPosition)) return; - if (!e.ctrlKey && this.props.Document.scrollY !== undefined) { + if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); return; } @@ -367,7 +367,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { e.stopPropagation(); // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix. - // if (e.ctrlKey && this.props.Document.scrollY === undefined) { + // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) { // let deltaScale = (1 - (e.deltaY / coefficient)); // let nw = this.nativeWidth * deltaScale; // let nh = this.nativeHeight * deltaScale; @@ -403,8 +403,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; - if (this.props.Document.scrollHeight !== undefined) this.props.Document.scrollY = this.isAnnotationOverlay ? newPanY : panY; - else this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 2759b46d4..e9207404e 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -53,8 +53,8 @@ export class PDFBox extends DocComponent(PdfDocumen Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); } this._reactionDisposer = reaction( - () => this.props.Document.scrollY, - () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }) + () => this.props.Document.panY, + () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.panY), behavior: "auto" }) ); } @@ -63,16 +63,16 @@ export class PDFBox extends DocComponent(PdfDocumen } public GetPage() { - return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.nativeHeight)) + 1; + return Math.floor(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1; } @action public BackPage() { - let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.nativeHeight)) + 1; + let cp = Math.ceil(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1; cp = cp - 1; if (cp > 0) { this.props.Document.curPage = cp; - this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); + this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); } } @@ -80,7 +80,7 @@ export class PDFBox extends DocComponent(PdfDocumen public GotoPage(p: number) { if (p > 0 && p <= NumCast(this.props.Document.numPages)) { this.props.Document.curPage = p; - this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.nativeHeight); + this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight); } } @@ -89,7 +89,7 @@ export class PDFBox extends DocComponent(PdfDocumen let cp = this.GetPage() + 1; if (cp <= NumCast(this.props.Document.numPages)) { this.props.Document.curPage = cp; - this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); + this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); } } @@ -181,7 +181,7 @@ export class PDFBox extends DocComponent(PdfDocumen onScroll = (e: React.UIEvent) => { if (e.currentTarget && this.containingCollectionDocument) { this.containingCollectionDocument.panTransformType = "None"; - this.containingCollectionDocument.scrollY = e.currentTarget.scrollTop; + this.containingCollectionDocument.panY = e.currentTarget.scrollTop; } } @@ -195,7 +195,7 @@ export class PDFBox extends DocComponent(PdfDocumen style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }} ref={this._mainCont}>
    - diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 021c04723..f3281047a 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -31,7 +31,7 @@ interface IViewerProps { fieldExtensionDoc: Doc; fieldKey: string; loaded: (nw: number, nh: number, np: number) => void; - scrollY: number; + panY: number; scrollTo: (y: number) => void; active: () => boolean; setPanY?: (n: number) => void; @@ -64,14 +64,14 @@ export class PDFViewer extends React.Component { private _searchString: string = ""; private _selectionText: string = ""; - @computed get scrollY(): number { return this.props.scrollY; } + @computed get panY(): number { return this.props.panY; } // startIndex: where to start rendering pages - @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.scrollY) - this._pageBuffer); } + @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.panY) - this._pageBuffer); } // endIndex: where to end rendering pages @computed get endIndex(): number { - return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer); + return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer); } @computed get filteredAnnotations() { @@ -81,7 +81,7 @@ export class PDFViewer extends React.Component { }); } - componentDidUpdate = (prevProps: IViewerProps) => this.scrollY !== prevProps.scrollY && this.renderPages(); + componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages(); componentDidMount = async () => { await this.initialLoad(); @@ -160,9 +160,8 @@ export class PDFViewer extends React.Component { })))); this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); - let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.scrollY)); + let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.panY)); this.props.setPanY && this.props.setPanY(startY); - this.props.Document.scrollY = startY + 1; } } @@ -435,7 +434,7 @@ export class PDFViewer extends React.Component { )}
    e.stopPropagation()} - style={{ bottom: -this.props.scrollY, left: `${this._searching ? 0 : 100}%` }}> + style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}>
    -
    {this.DocumentIcon}
    +
    {this.DocumentIcon()}
    {this.props.doc.type ? this.props.doc.type : "Other"}
    @@ -366,8 +362,8 @@ export class SearchItem extends React.Component {
    - {(doc1 instanceof Doc && doc2 instanceof Doc) ? this.props.doc.type === DocumentType.LINK ? : - : null} + {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? : + }
    ); -- cgit v1.2.3-70-g09d2 From 243beedb3a9e8be526d026fade0e8d48b507148a Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 7 Aug 2019 14:15:25 -0400 Subject: fixed defaults for treeview expansion. --- src/client/views/collections/CollectionTreeView.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 02b2583cd..571967743 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -27,7 +27,6 @@ import "./CollectionTreeView.scss"; import React = require("react"); import { ComputedField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; -import { exportNamedDeclaration } from 'babel-types'; export interface TreeViewProps { @@ -71,8 +70,9 @@ class TreeView extends React.Component { private _header?: React.RefObject = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); + get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; } @observable _collapsed: boolean = true; - @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "fields"); } + @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @computed get fieldKey() { @@ -341,10 +341,12 @@ class TreeView extends React.Component { let headerElements = ( { - this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : - this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : - this.treeViewExpandedView === "layout" && this.props.document.links ? "links" : - this.childDocs ? this.fieldKey : "fields"; + if (!this._collapsed) { + this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : + this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : + this.treeViewExpandedView === "layout" && this.props.document.links ? "links" : + this.childDocs ? this.fieldKey : "fields"; + } this._collapsed = false; })}> {this.treeViewExpandedView} -- cgit v1.2.3-70-g09d2 From 941c50617a0cbaef054f2be856de013bd69ea81f Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 7 Aug 2019 18:15:38 -0400 Subject: pdf annotaitons --- src/client/documents/Documents.ts | 1 + src/client/views/nodes/PDFBox.tsx | 3 +- src/client/views/pdf/Annotation.scss | 2 +- src/client/views/pdf/Annotation.tsx | 8 +-- src/client/views/pdf/PDFViewer.tsx | 95 ++++++++++++++---------------------- src/client/views/pdf/Page.tsx | 6 +-- 6 files changed, 47 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e804d5440..9c1278150 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -84,6 +84,7 @@ export interface DocumentOptions { templates?: List; viewType?: number; backgroundColor?: string; + opacity?: number; defaultBackgroundColor?: string; dropAction?: dropActionType; backgroundLayout?: string; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index e9207404e..6450cb826 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -194,10 +194,11 @@ export class PDFBox extends DocComponent(PdfDocumen onScroll={this.onScroll} style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }} ref={this._mainCont}> -
    +
    {this.settingsPanel()}
    ); diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss index 817115690..0c6df74f0 100644 --- a/src/client/views/pdf/Annotation.scss +++ b/src/client/views/pdf/Annotation.scss @@ -2,6 +2,6 @@ pointer-events: all; user-select: none; position: absolute; - background-color: pink; + background-color: red; opacity: 0.1; } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 2610f6c6e..7ba7b6d14 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -120,10 +120,10 @@ class RegionAnnotation extends React.Component { render() { return (
    ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f3281047a..95df10c58 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -36,6 +36,7 @@ interface IViewerProps { active: () => boolean; setPanY?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; } /** @@ -124,7 +125,7 @@ export class PDFViewer extends React.Component { if (this.props.active() && e.clipboardData) { e.clipboardData.setData("text/plain", this._selectionText); e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); - e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, 0, "#0390fc")[Id]); + e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, "#0390fc")[Id]); e.preventDefault(); } } @@ -133,7 +134,7 @@ export class PDFViewer extends React.Component { if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) { let linkDocId = e.clipboardData.getData("dash/linkDoc"); linkDocId && DocServer.GetRefField(linkDocId).then(async (link) => - (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), 0, "#0390fc", false))); + (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), "#0390fc", false))); } } @@ -166,32 +167,50 @@ export class PDFViewer extends React.Component { } @action - makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string, createLink: boolean = true): Doc => { + makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => { let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); + let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); let annoDocs: Doc[] = []; let minY = Number.MAX_VALUE; - this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { - let annoDoc = new Doc(); - if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale; - if (anno.style.top) annoDoc.y = parseInt(anno.style.top) / scale; - if (anno.style.height) annoDoc.height = parseInt(anno.style.height) / scale; - if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale; - annoDoc.page = key; + if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1 && !createLink) { + let anno = this._savedAnnotations.values()[0][0]; + let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) }); + if (anno.style.left) annoDoc.x = parseInt(anno.style.left); + if (anno.style.top) annoDoc.y = parseInt(anno.style.top); + if (anno.style.height) annoDoc.height = parseInt(anno.style.height); + if (anno.style.width) annoDoc.width = parseInt(anno.style.width); annoDoc.target = sourceDoc; annoDoc.group = mainAnnoDoc; annoDoc.color = color; annoDoc.type = AnnotationTypes.Region; annoDocs.push(annoDoc); + annoDoc.isBackground = true; anno.remove(); - (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); - })); - - let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); - mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); + this.props.addDocument && this.props.addDocument(annoDoc, false); + mainAnnoDoc = annoDoc; + mainAnnoDocProto = Doc.GetProto(annoDoc); + } else { + this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { + let annoDoc = new Doc(); + if (anno.style.left) annoDoc.x = parseInt(anno.style.left); + if (anno.style.top) annoDoc.y = parseInt(anno.style.top); + if (anno.style.height) annoDoc.height = parseInt(anno.style.height); + if (anno.style.width) annoDoc.width = parseInt(anno.style.width); + annoDoc.target = sourceDoc; + annoDoc.group = mainAnnoDoc; + annoDoc.color = color; + annoDoc.type = AnnotationTypes.Region; + annoDocs.push(annoDoc); + anno.remove(); + (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); + })); + + mainAnnoDocProto.y = Math.max(minY, 0); + mainAnnoDocProto.annotations = new List(annoDocs); + } mainAnnoDocProto.pdfDoc = this.props.Document; + mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); mainAnnoDocProto.annotationOn = this.props.Document; - mainAnnoDocProto.y = Math.max(minY, 0); - mainAnnoDocProto.annotations = new List(annoDocs); if (sourceDoc && createLink) { DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title)); } @@ -346,48 +365,6 @@ export class PDFViewer extends React.Component { this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); } - - // let viewer = this._viewer.current; - - // if (!this._pdfFindController) { - // if (container && viewer) { - // let simpleLinkService = new SimpleLinkService(); - // let pdfViewer = new PDFJSViewer.PDFViewer({ - // container: container, - // viewer: viewer, - // linkService: simpleLinkService - // }); - // simpleLinkService.setPdf(this.props.pdf); - // container.addEventListener("pagesinit", () => { - // pdfViewer.currentScaleValue = 1; - // }); - // container.addEventListener("pagerendered", () => { - // console.log("rendered"); - // this._pdfFindController.executeCommand('find', - // { - // caseSensitive: false, - // findPrevious: undefined, - // highlightAll: true, - // phraseSearch: true, - // query: searchString - // }); - // }); - // pdfViewer.setDocument(this.props.pdf); - // this._pdfFindController = new PDFJSViewer.PDFFindController(pdfViewer); - // // this._pdfFindController._linkService = pdfLinkService; - // pdfViewer.findController = this._pdfFindController; - // } - // } - // else { - // this._pdfFindController.executeCommand('find', - // { - // caseSensitive: false, - // findPrevious: undefined, - // highlightAll: true, - // phraseSearch: true, - // query: searchString - // }); - // } } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 6de2db427..4986f44d5 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -24,7 +24,7 @@ interface IPageProps { renderAnnotations: (annotations: Doc[], removeOld: boolean) => void; sendAnnotations: (annotations: HTMLDivElement[], page: number) => void; createAnnotation: (div: HTMLDivElement, page: number) => void; - makeAnnotationDocuments: (doc: Doc | undefined, scale: number, color: string, linkTo: boolean) => Doc; + makeAnnotationDocuments: (doc: Doc | undefined, color: string, linkTo: boolean) => Doc; getScrollFromPage: (page: number) => number; setSelectionText: (text: string) => void; } @@ -87,7 +87,7 @@ export default class Page extends React.Component { @action highlight = (targetDoc: Doc | undefined, color: string) => { // creates annotation documents for current highlights - let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, scale, color, false); + let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, color, false); Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc); return annotationDoc; } @@ -143,7 +143,7 @@ export default class Page extends React.Component { @action onPointerDown = (e: React.PointerEvent): void => { // if alt+left click, drag and annotate - if (this.props.Document.scale !== 1) return; + if (NumCast(this.props.Document.scale, 1) !== 1) return; if (e.altKey && e.button === 0) { e.stopPropagation(); } -- cgit v1.2.3-70-g09d2 From 50d7ffb3303cf06ced84b9788b95578fff2e92da Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 7 Aug 2019 20:14:53 -0400 Subject: pivot viewer native code --- src/client/views/Main.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 5 +- .../views/collections/CollectionViewChromes.tsx | 33 +++- .../collectionFreeForm/CollectionFreeFormView.tsx | 203 +++++++++++++++++---- 4 files changed, 205 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index daa778ed3..8be633f80 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -34,7 +34,7 @@ let swapDocs = async () => { DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email); await Docs.Prototypes.initialize(); await CurrentUserUtils.loadUserDocument(info); - await PivotView.loadLayouts(); + // await PivotView.loadLayouts(); // updates old user documents to prevent chrome on tree view. (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 60ede17e7..7a402798e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -89,8 +89,7 @@ export class CollectionView extends React.Component { subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; - delete this.props.Document.arrangeInit; - delete this.props.Document.arrangeScript; + delete this.props.Document.usePivotLayout; }, icon: "signature" }); if (CollectionBaseView.InSafeMode()) { @@ -103,7 +102,7 @@ export class CollectionView extends React.Component { switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) }); - subItems.push({ description: "Pivot", icon: "copy", event: () => CollectionFreeFormView.SetPivotLayout(this.props.Document) }); + subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true }); break; } } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 1b2561953..e1ac79da6 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -3,7 +3,7 @@ import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import { CollectionViewType } from "./CollectionBaseView"; import { undoBatch } from "../../util/UndoManager"; -import { action, observable, runInAction, computed, IObservable, IObservableValue } from "mobx"; +import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { DocLike } from "../MetadataEntryMenu"; @@ -187,6 +187,36 @@ export class CollectionViewBaseChrome extends React.Component { + if (!this.document.usePivotLayout) { + return (null); + } + return () => this.pivotKeyDisplay = e.currentTarget.value)} + onKeyPress={action((e: React.KeyboardEvent) => { + let value = e.currentTarget.value; + if (e.which === 13) { + this.pivotKey = value; + this.pivotKeyDisplay = ""; + } + })} />); + } + render() { return (
    @@ -219,6 +249,7 @@ export class CollectionViewBaseChrome extends React.Component { }} onPointerDown={this.openViewSpecs} /> + {this.getPivotInput()}
    { + let collection = target.Document; + const field = StrCast(collection.pivotField) || "title"; + const width = NumCast(collection.pivotWidth) || 200; + + const groups = new Map, Doc[]>(); + + for (const doc of target.childDocs) { + const val = doc[field]; + if (val === undefined) continue; + + const l = groups.get(val); + if (l) { + l.push(doc); + } else { + groups.set(val, [doc]); + } + + } + + let minSize = Infinity; + + groups.forEach((val, key) => { + minSize = Math.min(minSize, val.length); + }); + + const numCols = NumCast(collection.pivotNumColumns) || Math.ceil(Math.sqrt(minSize)); + const fontSize = NumCast(collection.pivotFontSize); + + const docMap = new Map(); + const groupNames: PivotData[] = []; + + let x = 0; + groups.forEach((val, key) => { + let y = 0; + let xCount = 0; + groupNames.push({ + type: "text", + text: String(key), + x, + y: width + 50, + width: width * 1.25 * numCols, + height: 100, fontSize: fontSize + }); + for (const doc of val) { + docMap.set(doc, { + x: x + xCount * width * 1.25, + y: -y, + width, + height: width + }); + xCount++; + if (xCount >= numCols) { + xCount = 0; + y += width * 1.25; + } + } + x += width * 1.25 * (numCols + 1); + }); + + let elements = target.viewDefsToJSX(groupNames); + let curPage = FieldValue(target.Document.curPage, -1); + + let docViews = target.childDocs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { + var page = NumCast(doc.page, -1); + if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { + let minim = BoolCast(doc.isMinimized); + if (minim === undefined || !minim) { + let defaultPosition = (): ViewDefBounds => { + return { + x: NumCast(doc.x), + y: NumCast(doc.y), + z: NumCast(doc.z), + width: NumCast(doc.width), + height: NumCast(doc.height) + }; + }; + const pos = docMap.get(doc) || defaultPosition(); + prev.push({ + ele: ( + ), + bounds: { + x: pos.x, + y: pos.y, + z: pos.z, + width: NumCast(pos.width), + height: NumCast(pos.height) + } + }); + } + } + return prev; + }, elements); + + target.resetSelectOnLoaded(); + + return docViews; + }; + } type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; @@ -508,9 +638,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return 1; } - getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps { - let self = this; let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout); return { DataDoc: pair.data, @@ -569,7 +697,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result; } - private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } } | undefined { + viewDefsToJSX = (views: any[]) => { + let elements: ViewDefResult[] = []; + if (Array.isArray(views)) { + elements = views.reduce((prev, ele) => { + const jsx = this.viewDefToJSX(ele); + jsx && prev.push(jsx); + return prev; + }, elements); + } + return elements; + } + + private viewDefToJSX(viewDef: any): Opt { if (viewDef.type === "text") { const text = Cast(viewDef.text, "string"); const x = Cast(viewDef.x, "number"); @@ -598,20 +738,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const script = this.Document.arrangeScript; let state: any = undefined; const docs = this.childDocs; - let elements: { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } }[] = []; + let elements: ViewDefResult[] = []; if (initScript) { const initResult = initScript.script.run({ docs, collection: this.Document }); if (initResult.success) { const result = initResult.result; const { state: scriptState, views } = result; state = scriptState; - if (Array.isArray(views)) { - elements = views.reduce((prev, ele) => { - const jsx = this.viewDefToJSX(ele); - jsx && prev.push(jsx); - return prev; - }, elements); - } + elements = this.viewDefsToJSX(views); } } let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { @@ -633,14 +767,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return prev; }, elements); - setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way .... + this.resetSelectOnLoaded(); return docviews; } + resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way .... + @computed.struct get views() { - return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + let source = this.Document.usePivotLayout === true ? PivotView.elements(this) : this.elements; + return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed.struct get overlayViews() { @@ -798,22 +935,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - public static SetPivotLayout = (target: Doc) => { - let setSpecifiedLayoutField = (originalText: string, key: string, params: Record, requiredType?: string) => { - const script = CompileScript(originalText, { - params, - requiredType, - typecheck: false - }); - if (!script.compiled) { - console.log(script.errors.map(error => error.messageText).join("\n")); - return; - } - target[key] = new ScriptField(script); - }; - setSpecifiedLayoutField(PivotView.scripts.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined); - setSpecifiedLayoutField(PivotView.scripts.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); - } + // public static SetPivotLayout = (target: Doc) => { + // let setSpecifiedLayoutField = (originalText: string, key: string, params: Record, requiredType?: string) => { + // const script = CompileScript(originalText, { + // params, + // requiredType, + // typecheck: false + // }); + // if (!script.compiled) { + // console.log(script.errors.map(error => error.messageText).join("\n")); + // return; + // } + // target[key] = new ScriptField(script); + // }; + // setSpecifiedLayoutField(PivotView.scripts.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined); + // setSpecifiedLayoutField(PivotView.scripts.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); + // } render() { const easing = () => this.props.Document.panTransformType === "Ease"; -- cgit v1.2.3-70-g09d2 From 35cb61f0d93983464b29152f159d09ea6bd4edf9 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 7 Aug 2019 21:37:14 -0400 Subject: Mostly have field level read only working --- src/client/DocServer.ts | 34 +++++++++++++++++++++++++++++++++- src/client/views/MainView.tsx | 8 ++++++++ src/client/views/nodes/WebBox.tsx | 3 ++- src/new_fields/Doc.ts | 38 +++++++++++++++++++++++++++++++------- src/new_fields/util.ts | 11 +++++++++-- src/server/GarbageCollector.ts | 2 +- 6 files changed, 84 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 87a87be92..5af89cf49 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,6 +1,6 @@ import * as OpenSocket from 'socket.io-client'; import { MessageStore, Diff, YoutubeQueryTypes } from "./../server/Message"; -import { Opt } from '../new_fields/Doc'; +import { Opt, Doc } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; @@ -26,6 +26,38 @@ export namespace DocServer { let GUID: string; // indicates whether or not a document is currently being udpated, and, if so, its id + export enum WriteMode { + Always = 0, + None = 1, + SameUser = 2, + } + + const fieldWriteModes: { [field: string]: WriteMode } = {}; + const docsWithUpdates: { [field: string]: Doc[] } = {}; + + export function setFieldWriteMode(field: string, writeMode: WriteMode) { + fieldWriteModes[field] = writeMode; + if (writeMode === WriteMode.Always) { + const docs = docsWithUpdates[field]; + if (docs) { + docs.forEach(doc => Doc.RunCachedUpdate(doc, field)); + delete docsWithUpdates[field]; + } + } + } + + export function getFieldWriteMode(field: string) { + return fieldWriteModes[field]; + } + + export function registerDocWithCachedUpdate(doc: Doc, field: string) { + let list = docsWithUpdates[field]; + if (!list) { + list = docsWithUpdates[field] = []; + } + list.push(doc); + } + export function init(protocol: string, hostname: string, port: number, identifier: string) { _cache = {}; GUID = identifier; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 53f589684..7bc31e961 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -422,6 +422,14 @@ export class MainView extends React.Component {
    )}
  • +