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 --- .../views/collections/CollectionBaseView.tsx | 18 +++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 92 +++++++++++++--------- 2 files changed, 74 insertions(+), 36 deletions(-) (limited to 'src/client/views/collections') 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" }); } -- 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/client/views/collections') 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 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/client/views/collections') 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 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/client/views/collections') 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 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/client/views/collections') 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 {
)}
  • -
  • +
  • +
  • +
  • +