aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--src/Utils.ts15
-rw-r--r--src/client/DocServer.ts94
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx8
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts32
-rw-r--r--src/client/documents/Documents.ts72
-rw-r--r--src/client/util/DictationManager.ts366
-rw-r--r--src/client/util/DocumentManager.ts22
-rw-r--r--src/client/util/DragManager.ts15
-rw-r--r--src/client/util/LinkManager.ts14
-rw-r--r--src/client/util/SerializationHelper.ts4
-rw-r--r--src/client/util/type_decls.d1
-rw-r--r--src/client/views/ContextMenuItem.tsx2
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/GlobalKeyHandler.ts19
-rw-r--r--src/client/views/InkingCanvas.tsx2
-rw-r--r--src/client/views/InkingControl.tsx4
-rw-r--r--src/client/views/Main.scss29
-rw-r--r--src/client/views/Main.tsx5
-rw-r--r--src/client/views/MainView.tsx109
-rw-r--r--src/client/views/SearchItem.tsx6
-rw-r--r--src/client/views/TemplateMenu.tsx41
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx25
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx33
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx67
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx4
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx16
-rw-r--r--src/client/views/collections/CollectionStackingView.scss4
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx122
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx44
-rw-r--r--src/client/views/collections/CollectionView.tsx21
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx33
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx495
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx22
-rw-r--r--src/client/views/nodes/ButtonBox.tsx2
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx11
-rw-r--r--src/client/views/nodes/DocumentView.tsx129
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx18
-rw-r--r--src/client/views/nodes/ImageBox.tsx51
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx1
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx17
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.scss67
-rw-r--r--src/client/views/nodes/PDFBox.tsx273
-rw-r--r--src/client/views/nodes/WebBox.tsx3
-rw-r--r--src/client/views/pdf/Annotation.scss7
-rw-r--r--src/client/views/pdf/Annotation.tsx109
-rw-r--r--src/client/views/pdf/PDFAnnotationLayer.scss6
-rw-r--r--src/client/views/pdf/PDFAnnotationLayer.tsx7
-rw-r--r--src/client/views/pdf/PDFMenu.tsx123
-rw-r--r--src/client/views/pdf/PDFViewer.scss192
-rw-r--r--src/client/views/pdf/PDFViewer.tsx743
-rw-r--r--src/client/views/pdf/Page.scss31
-rw-r--r--src/client/views/pdf/Page.tsx331
-rw-r--r--src/client/views/presentationview/PresentationElement.tsx7
-rw-r--r--src/client/views/search/FilterBox.tsx2
-rw-r--r--src/client/views/search/SearchItem.tsx65
-rw-r--r--src/client/views/search/ToggleBar.tsx2
-rw-r--r--src/debug/Test.tsx88
-rw-r--r--src/new_fields/Doc.ts133
-rw-r--r--src/new_fields/Proxy.ts29
-rw-r--r--src/new_fields/ScriptField.ts12
-rw-r--r--src/new_fields/Types.ts8
-rw-r--r--src/new_fields/util.ts43
-rw-r--r--src/scraping/buxton/scraper.py21
-rw-r--r--src/scraping/buxton/source/Bill_Notes_3_button_optical_mouse.docxbin0 -> 412208 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Amazon_Kindle_Keyboard.docxbin0 -> 474022 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Apple_Adj_Keyboard.docxbin0 -> 1758498 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Apple_Mac_Portable.docxbin0 -> 748412 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_BAT.docxbin0 -> 1349620 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docxbin1561425 -> 1675500 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Casio_CZ-101.docxbin0 -> 523939 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_FrogPad.docxbin679241 -> 840173 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docxbin1531689 -> 1729610 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docxbin1933004 -> 2094142 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Kindle_3G_lighted_cover.docxbin0 -> 919789 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Microwriter.docxbin0 -> 1042556 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_NewO.docxbin2150143 -> 2264571 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_PARCkbd.docxbin517484 -> 631959 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_PARCtab.docbin0 -> 4046250 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Twiddler.docxbin0 -> 526307 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_orbiTouch.docbin0 -> 3945306 bytes
-rw-r--r--src/server/GarbageCollector.ts2
-rw-r--r--src/server/index.ts29
-rw-r--r--src/server/remapUrl.ts2
87 files changed, 2354 insertions, 1965 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index c544bc837..5b35884bd 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index 8df67df5d..959b89fe5 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 {
@@ -132,6 +133,8 @@ export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => vo
return dup;
}
+export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); }
+
export function returnTrue() { return true; }
export function returnFalse() { return false; }
@@ -140,6 +143,8 @@ export function returnOne() { return 1; }
export function returnZero() { return 0; }
+export function returnEmptyString() { return ""; }
+
export function emptyFunction() { }
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@@ -171,4 +176,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/DocServer.ts b/src/client/DocServer.ts
index fc39fa364..bf5168c22 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,42 @@ export namespace DocServer {
let GUID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
+ export enum WriteMode {
+ Default = 0, //Anything goes
+ Playground = 1,
+ LiveReadonly = 2,
+ LivePlayground = 3,
+ }
+
+ const fieldWriteModes: { [field: string]: WriteMode } = {};
+ const docsWithUpdates: { [field: string]: Set<Doc> } = {};
+
+ export function setFieldWriteMode(field: string, writeMode: WriteMode) {
+ fieldWriteModes[field] = writeMode;
+ if (writeMode !== WriteMode.Playground) {
+ const docs = docsWithUpdates[field];
+ if (docs) {
+ docs.forEach(doc => Doc.RunCachedUpdate(doc, field));
+ delete docsWithUpdates[field];
+ }
+ }
+ }
+
+ export function getFieldWriteMode(field: string) {
+ return fieldWriteModes[field] || WriteMode.Default;
+ }
+
+ export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
+ let list = docsWithUpdates[field];
+ if (!list) {
+ list = docsWithUpdates[field] = new Set;
+ }
+ if (!list.has(doc)) {
+ Doc.AddCachedUpdate(doc, field, oldValue);
+ list.add(doc);
+ }
+ }
+
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
GUID = identifier;
@@ -124,13 +160,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 +249,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<RefField>[] = [];
+ const proms: Promise<void>[] = [];
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 +289,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/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<FieldViewProps> {
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<FieldViewProps> {
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<FieldViewProps> {
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/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index cc366abc2..954a05585 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -10,9 +10,9 @@ import requestPromise = require("request-promise");
import { List } from "../../new_fields/List";
import { ClientRecommender } from "../ClientRecommender";
-type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor, analyzer: AnalysisApplier };
+type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor };
type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
-type AnalysisApplier = (target: Doc, relevantKeys: string[], ...args: any) => any;
+type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any;
type BodyConverter<D> = (data: D) => string;
type Converter = (results: any) => Field;
@@ -42,7 +42,7 @@ export enum Confidence {
*/
export namespace CognitiveServices {
- const ExecuteQuery = async <D, R>(service: Service, manager: APIManager<D>, data: D): Promise<Opt<R>> => {
+ const ExecuteQuery = async <D>(service: Service, manager: APIManager<D>, data: D): Promise<any> => {
return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
let apiKey = await response.text();
if (!apiKey) {
@@ -50,7 +50,7 @@ export namespace CognitiveServices {
return undefined;
}
- let results: Opt<R>;
+ let results: any;
try {
results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json));
} catch {
@@ -103,7 +103,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<string> = async (target: Doc, keys: string[], url: string, service: Service, converter: Converter) => {
let batch = UndoManager.StartBatch("Image Analysis");
let storageKey = keys[0];
@@ -111,7 +115,7 @@ export namespace CognitiveServices {
return;
}
let toStore: any;
- let results = await ExecuteQuery<string, any>(service, Manager, url);
+ let results = await ExecuteQuery(service, Manager, url);
if (!results) {
toStore = "Cognitive Services could not process the given image URL.";
} else {
@@ -124,9 +128,9 @@ export namespace CognitiveServices {
target[storageKey] = toStore;
batch.end();
- }
+ };
- };
+ }
export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
@@ -183,10 +187,14 @@ export namespace CognitiveServices {
return new Promise<any>(promisified);
},
- analyzer: async (target: Doc, keys: string[], inkData: InkData) => {
+ };
+
+ export namespace Appliers {
+
+ export const ConcatenateHandwriting: AnalysisApplier<InkData> = async (target: Doc, keys: string[], inkData: InkData) => {
let batch = UndoManager.StartBatch("Ink Analysis");
- let results = await ExecuteQuery<InkData, any>(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");
@@ -196,9 +204,9 @@ export namespace CognitiveServices {
}
batch.end();
- }
+ };
- };
+ }
export interface AzureStrokeData {
id: number;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2a1f63d59..7dd853156 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,46 +46,26 @@ 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";
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";
+import { ProxyField } from "../../new_fields/Proxy";
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;
@@ -82,6 +83,8 @@ export interface DocumentOptions {
templates?: List<string>;
viewType?: number;
backgroundColor?: string;
+ opacity?: number;
+ defaultBackgroundColor?: string;
dropAction?: dropActionType;
backgroundLayout?: string;
chromeStatus?: string;
@@ -122,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 },
@@ -193,6 +196,8 @@ export namespace Docs {
* haven't been initialized, the newly initialized prototype document.
*/
export async function initialize(): Promise<void> {
+ 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
@@ -591,23 +596,20 @@ export namespace Docs {
export namespace DocUtils {
- export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default", sourceContext?: Doc) {
+ export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) {
if (LinkManager.Instance.doesLinkExist(source, target)) return undefined;
let sv = DocumentManager.Instance.getDocumentView(source);
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(() => {
- linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: "100%" });
- linkDoc.type = DocumentType.LINK;
- let linkDocProto = Doc.GetProto(linkDoc);
+ linkDocProto.type = DocumentType.LINK;
linkDocProto.targetContext = targetContext;
linkDocProto.sourceContext = sourceContext;
linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
linkDocProto.linkDescription = description;
- linkDocProto.linkTags = tags;
linkDocProto.type = DocumentType.LINK;
linkDocProto.anchor1 = source;
@@ -617,10 +619,14 @@ export namespace DocUtils {
linkDocProto.anchor2Page = target.curPage;
linkDocProto.anchor2Groups = new List<Doc>([]);
- LinkManager.Instance.addLink(linkDoc);
+ LinkManager.Instance.addLink(linkDocProto);
+ 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;
+ return linkDocProto;
}
}
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index b58bdb6c7..9c61fe125 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,38 +1,348 @@
-namespace CORE {
- export interface IWindow extends Window {
- webkitSpeechRecognition: any;
+import { SelectionManager } from "./SelectionManager";
+import { DocumentView } from "../views/nodes/DocumentView";
+import { UndoManager } from "./UndoManager";
+import * as interpreter from "words-to-numbers";
+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 { 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";
+import { Utils } from "../../Utils";
+
+/**
+ * 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;
+ }
}
-}
+ const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
+ export const placeholder = "Listening...";
-const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
+ export namespace Controls {
-export default class DictationManager {
- public static Instance = new DictationManager();
- private isListening = false;
- private recognizer: any;
+ const infringe = "unable to process: dictation manager still involved in previous session";
+ const intraSession = ". ";
+ const interSession = " ... ";
- constructor() {
- this.recognizer = new webkitSpeechRecognition();
- this.recognizer.interimResults = false;
- this.recognizer.continuous = true;
- }
+ let isListening = false;
+ let isManuallyStopped = false;
- finish = (handler: any, data: any) => {
- handler(data);
- this.isListening = false;
- this.recognizer.stop();
- }
+ let current: string | undefined = undefined;
+ let sessionResults: string[] = [];
+
+ const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition();
+ recognizer.onstart = () => console.log("initiating speech recognition session...");
+
+ 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;
- listen = () => {
- if (this.isListening) {
- return undefined;
+ export interface ListeningOptions {
+ language: string;
+ continuous: ContinuityArgs;
+ delimiters: DelimiterArgs;
+ interimHandler: InterimResultHandler;
+ tryExecute: boolean;
}
- this.isListening = true;
- this.recognizer.start();
- return new Promise<string>((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 const listen = async (options?: Partial<ListeningOptions>) => {
+ let results: string | undefined;
+ let main = MainView.Instance;
+
+ main.dictationOverlayVisible = true;
+ main.isListening = { interim: false };
+
+ try {
+ results = await listenImpl(options);
+ if (results) {
+ Utils.CopyText(results);
+ main.isListening = false;
+ let execute = options && options.tryExecute;
+ main.dictatedPhrase = execute ? results.toLowerCase() : results;
+ main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+ }
+ } catch (e) {
+ main.isListening = false;
+ main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+ main.dictationSuccess = false;
+ } finally {
+ main.initiateDictationFade();
+ }
+
+ return results;
+ };
+
+ const listenImpl = (options?: Partial<ListeningOptions>) => {
+ if (isListening) {
+ return infringe;
+ }
+ isListening = true;
+
+ let handler = options ? options.interimHandler : undefined;
+ let continuous = options ? options.continuous : undefined;
+ let indefinite = continuous && continuous.indefinite;
+ let language = options ? options.language : 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;
+ recognizer.lang = language === undefined ? "en-US" : language;
+
+ recognizer.start();
+
+ return new Promise<string>((resolve, reject) => {
+
+ recognizer.onerror = (e: SpeechRecognitionError) => {
+ if (!(indefinite && e.error === "no-speech")) {
+ recognizer.stop();
+ reject(e);
+ }
+ };
+
+ recognizer.onresult = (e: SpeechRecognitionEvent) => {
+ current = synthesize(e, intra);
+ handler && handler(current);
+ isManuallyStopped && complete();
+ };
+
+ recognizer.onend = (e: Event) => {
+ if (!indefinite || isManuallyStopped) {
+ return complete();
+ }
+
+ if (current) {
+ sessionResults.push(current);
+ current = undefined;
+ }
+ recognizer.start();
+ };
+
+ let complete = () => {
+ if (indefinite) {
+ current && sessionResults.push(current);
+ sessionResults.length && resolve(sessionResults.join(inter || interSession));
+ } else {
+ resolve(current);
+ }
+ reset();
+ };
+
+ });
+ };
+
+ export const stop = (salvageSession = true) => {
+ if (!isListening) {
+ return;
+ }
+ isManuallyStopped = true;
+ salvageSession ? recognizer.stop() : recognizer.abort();
+ let main = MainView.Instance;
+ if (main.dictationOverlayVisible) {
+ main.cancelDictationFade();
+ main.dictationOverlayVisible = false;
+ main.dictationSuccess = undefined;
+ setTimeout(() => main.dictatedPhrase = placeholder, 500);
+ }
+ };
+
+ 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 || intraSession);
+ };
+
+ const reset = () => {
+ current = undefined;
+ sessionResults = [];
+ isListening = false;
+ isManuallyStopped = false;
+ recognizer.onresult = null;
+ recognizer.onerror = null;
+ recognizer.onend = null;
+ };
+
+ }
+
+ export namespace Commands {
+
+ export const dictationFadeDuration = 2000;
+
+ export type IndependentAction = (target: DocumentView) => any | Promise<any>;
+ export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
+
+ export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+ 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) => {
+ return UndoManager.RunInBatch(async () => {
+ 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)) {
+ await entry.action(target);
+ success = true;
+ }
+ }
+ return success;
+ }
+
+ 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<DocumentType, CastCtor>([
+ [DocumentType.COL, listSpec(Doc)],
+ [DocumentType.AUDIO, AudioField],
+ [DocumentType.IMG, ImageField],
+ [DocumentType.HIST, HistogramField],
+ [DocumentType.IMPORT, listSpec(Doc)],
+ [DocumentType.TEXT, "string"]
+ ]);
+
+ 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;
+ };
+
+ const validate = (target: DocumentView, types: DocumentType[]) => {
+ for (let type of types) {
+ if (tryCast(target, type)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ const interpretNumber = (number: string) => {
+ let initial = parseInt(number);
+ if (!isNaN(initial)) {
+ return initial;
+ }
+ let converted = interpreter.wordsToNumbers(number, { fuzzy: true });
+ if (converted === null) {
+ return NaN;
+ }
+ return typeof converted === "string" ? parseInt(converted) : converted;
+ };
+
+ const Independent = new Map<string, IndependentEntry>([
+
+ ["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");
+ }
+ }],
+
+ ["promote", {
+ action: (target: DocumentView) => {
+ console.log(target);
+ },
+ restrictTo: [DocumentType.TEXT]
+ }]
+
+ ]);
+
+ const Dependent = new Array<DependentEntry>(
+
+ {
+ 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]
+ },
+
+ {
+ 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]
+ }
+
+ );
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 32f728c71..7f526b247 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
import { action, computed, observable } from 'mobx';
-import { Doc } from '../../new_fields/Doc';
+import { Doc, DocListCastAsync } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
-import { BoolCast, Cast, NumCast } from '../../new_fields/Types';
+import { Cast, NumCast } from '../../new_fields/Types';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionPDFView } from '../views/collections/CollectionPDFView';
import { CollectionVideoView } from '../views/collections/CollectionVideoView';
@@ -104,7 +104,7 @@ export class DocumentManager {
@computed
public get LinkedDocumentViews() {
- let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush)).reduce((pairs, dv) => {
+ let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)).reduce((pairs, dv) => {
let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
pairs.push(...linksList.reduce((pairs, link) => {
if (link) {
@@ -138,15 +138,17 @@ export class DocumentManager {
let docView: DocumentView | null;
// using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed
if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
- docView.props.Document.libraryBrush = true;
+ Doc.BrushDoc(docView.props.Document);
if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
- UndoManager.RunInBatch(() => {
- docView!.props.focus(docView!.props.Document, willZoom);
- }, "focus");
+ UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
} else {
if (!contextDoc) {
- if (docContext) {
+ let docs = docContext ? await DocListCastAsync(docContext.data) : undefined;
+ let found = false;
+ docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate));
+ if (docContext && found) {
let targetContextView: DocumentView | null;
+
if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) {
docContext.panTransformType = "Ease";
targetContextView.props.focus(docDelegate, willZoom);
@@ -158,13 +160,13 @@ export class DocumentManager {
}
} else {
const actualDoc = Doc.MakeAlias(docDelegate);
- actualDoc.libraryBrush = true;
+ Doc.BrushDoc(actualDoc);
if (linkPage !== undefined) actualDoc.curPage = linkPage;
(dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, undefined);
}
} else {
let contextView: DocumentView | null;
- docDelegate.libraryBrush = true;
+ Doc.BrushDoc(docDelegate);
if (!forceDockFunc && (contextView = DocumentManager.Instance.getDocumentView(contextDoc))) {
contextDoc.panTransformType = "Ease";
contextView.props.focus(docDelegate, willZoom);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index abcc3a4e1..a7aaaed7c 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 {
@@ -399,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;
@@ -423,13 +429,16 @@ 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]})`)
);
};
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/util/LinkManager.ts b/src/client/util/LinkManager.ts
index a647f22c1..8a668e8d8 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,8 @@ 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/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<any> {
+ export async function Deserialize(obj: any): Promise<any> {
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/client/util/type_decls.d b/src/client/util/type_decls.d
index 79a4e50d5..622e10960 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -74,6 +74,7 @@ interface String {
normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string;
normalize(form?: string): string;
repeat(count: number): string;
+ replace(a:any, b:any):string; // bcz: fix this
startsWith(searchString: string, position?: number): boolean;
anchor(name: string): string;
big(): string;
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<ContextMenuProps & { select
handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
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/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 15471371a..6616d5d58 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -562,7 +562,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
} else {
dW && (doc.width = actualdW);
dH && (doc.height = actualdH);
- dH && Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
+ dH && element.props.Document.autoHeight && Doc.SetInPlace(element.props.Document, "autoHeight", false, true);
}
}
});
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index ea2e3e196..e773014e3 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -3,12 +3,9 @@ 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";
-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<KeyControlInfo>;
@@ -62,7 +59,8 @@ export default class KeyManager {
private unmodified = action((keyname: string, e: KeyboardEvent) => {
switch (keyname) {
case "escape":
- if (MainView.Instance.isPointerDown) {
+ let main = MainView.Instance;
+ if (main.isPointerDown) {
DragManager.AbortDrag();
} else {
if (CollectionDockingView.Instance.HasFullScreen()) {
@@ -71,8 +69,9 @@ export default class KeyManager {
SelectionManager.DeselectAll();
}
}
- MainView.Instance.toggleColorPicker(true);
+ main.toggleColorPicker(true);
SelectionManager.DeselectAll();
+ DictationManager.Controls.stop();
break;
case "delete":
case "backspace":
@@ -106,13 +105,9 @@ export default class KeyManager {
switch (keyname) {
case " ":
- let transcript = await DictationManager.Instance.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();
+ DictationManager.Controls.listen({ tryExecute: true });
stopPropagation = true;
preventDefault = true;
- break;
}
return {
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<InkCanvasProps> {
}
render() {
- let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
+ let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
return (
<div className="inkingCanvas">
<div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
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/Main.scss b/src/client/views/Main.scss
index eed2ae4fa..f76abaff3 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -266,4 +266,33 @@ ul#add-options-list {
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;
+ z-index: 999;
+ transition: 0.5s all ease;
+ pointer-events: none;
} \ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 1cf13aa74..0e687737d 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(<MainView />, document.getElementById('root'));
})(); \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 97964166a..419b15697 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';
@@ -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';
@@ -40,6 +40,7 @@ import { CollectionTreeView } from './collections/CollectionTreeView';
import { ClientUtils } from '../util/ClientUtils';
import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
//import { DocumentManager } from '../util/DocumentManager';
+import { DictationManager } from '../util/DictationManager';
@observer
export class MainView extends React.Component {
@@ -48,6 +49,30 @@ export class MainView extends React.Component {
@observable private _workspacesShown: boolean = false;
@observable public pwidth: number = 0;
@observable public pheight: number = 0;
+
+ @observable private dictationState = DictationManager.placeholder;
+ @observable private dictationSuccessState: boolean | undefined = undefined;
+ @observable private dictationDisplayState = false;
+ @observable private dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
+
+ public overlayTimeout: NodeJS.Timeout | undefined;
+
+ public initiateDictationFade = () => {
+ let duration = DictationManager.Commands.dictationFadeDuration;
+ this.overlayTimeout = setTimeout(() => {
+ this.dictationOverlayVisible = false;
+ this.dictationSuccess = undefined;
+ setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500);
+ }, duration);
+ }
+
+ public cancelDictationFade = () => {
+ if (this.overlayTimeout) {
+ clearTimeout(this.overlayTimeout);
+ this.overlayTimeout = undefined;
+ }
+ }
+
@computed private get mainContainer(): Opt<Doc> {
return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
}
@@ -65,6 +90,38 @@ export class MainView extends React.Component {
}
}
+ @computed public get dictatedPhrase() {
+ return this.dictationState;
+ }
+
+ public set dictatedPhrase(value: string) {
+ runInAction(() => this.dictationState = value);
+ }
+
+ @computed public get dictationSuccess() {
+ return this.dictationSuccessState;
+ }
+
+ public set dictationSuccess(value: boolean | undefined) {
+ runInAction(() => this.dictationSuccessState = value);
+ }
+
+ @computed public get dictationOverlayVisible() {
+ return this.dictationDisplayState;
+ }
+
+ public set dictationOverlayVisible(value: boolean) {
+ runInAction(() => this.dictationDisplayState = value);
+ }
+
+ @computed public get isListening() {
+ return this.dictationListeningState;
+ }
+
+ public set isListening(value: DictationManager.Controls.ListeningUIStatus) {
+ runInAction(() => this.dictationListeningState = value);
+ }
+
componentWillMount() {
var tag = document.createElement('script');
@@ -271,6 +328,7 @@ export class MainView extends React.Component {
PanelWidth={this.getPWidth}
PanelHeight={this.getPHeight}
renderDepth={0}
+ backgroundColor={returnEmptyString}
selectOnLoad={false}
focus={emptyFunction}
parentActive={returnTrue}
@@ -335,6 +393,7 @@ export class MainView extends React.Component {
renderDepth={0}
selectOnLoad={false}
focus={emptyFunction}
+ backgroundColor={returnEmptyString}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -404,6 +463,20 @@ export class MainView extends React.Component {
];
if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
+ const setWriteMode = (mode: DocServer.WriteMode) => {
+ console.log(DocServer.WriteMode[mode]);
+ const mode1 = mode;
+ const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+ DocServer.setFieldWriteMode("x", mode1);
+ DocServer.setFieldWriteMode("y", mode1);
+ DocServer.setFieldWriteMode("width", mode1);
+ DocServer.setFieldWriteMode("height", mode1);
+
+ DocServer.setFieldWriteMode("panX", mode2);
+ DocServer.setFieldWriteMode("panY", mode2);
+ DocServer.setFieldWriteMode("scale", mode2);
+ DocServer.setFieldWriteMode("viewType", mode2);
+ };
return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} >
<input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
<label htmlFor="add-menu-toggle" style={{ marginTop: 2 }} title="Add Node"><p>+</p></label>
@@ -421,6 +494,12 @@ export class MainView extends React.Component {
</button>
</div></li>)}
<li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
+ {ClientUtils.RELEASE ? [] : [
+ <li key="test"><button className="add-button round-button" title="Default" onClick={() => setWriteMode(DocServer.WriteMode.Default)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
+ <li key="test1"><button className="add-button round-button" title="Playground" onClick={() => setWriteMode(DocServer.WriteMode.Playground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
+ <li key="test2"><button className="add-button round-button" title="Live Playground" onClick={() => setWriteMode(DocServer.WriteMode.LivePlayground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
+ <li key="test3"><button className="add-button round-button" title="Live Readonly" onClick={() => setWriteMode(DocServer.WriteMode.LiveReadonly)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
+ ]}
<li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
<div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
<SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
@@ -469,9 +548,35 @@ export class MainView extends React.Component {
this.isSearchVisible = !this.isSearchVisible;
}
+ private get dictationOverlay() {
+ let display = this.dictationOverlayVisible;
+ let success = this.dictationSuccess;
+ let result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`;
+ return (
+ <div>
+ <div
+ className={"dictation-prompt"}
+ style={{
+ opacity: display ? 1 : 0,
+ background: success === undefined ? "gainsboro" : success ? "lawngreen" : "red",
+ borderColor: this.isListening ? "red" : "black",
+ }}
+ >{result}</div>
+ <div
+ className={"dictation-prompt-overlay"}
+ style={{
+ opacity: display ? 0.4 : 0,
+ backgroundColor: this.isListening ? "red" : "darkslategrey"
+ }}
+ />
+ </div>
+ );
+ }
+
render() {
return (
<div id="main-div">
+ {this.dictationOverlay}
<DocumentDecorations />
{this.mainContent}
<PreviewCursor />
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
index 13e4b88f7..fd4b2420d 100644
--- a/src/client/views/SearchItem.tsx
+++ b/src/client/views/SearchItem.tsx
@@ -37,12 +37,10 @@ export class SearchItem extends React.Component<SearchProps> {
return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
}
onPointerEnter = (e: React.PointerEvent) => {
- this.props.doc.libraryBrush = true;
- Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+ Doc.BrushDoc(this.props.doc);
}
onPointerLeave = (e: React.PointerEvent) => {
- this.props.doc.libraryBrush = false;
- Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+ Doc.UnBrushDoc(this.props.doc);
}
collectionRef = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1b32f0ddd..393e97a7e 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,43 @@ export interface TemplateMenuProps {
@observer
export class TemplateMenu extends React.Component<TemplateMenuProps> {
@observable private _hidden: boolean = true;
+ dragRef = React.createRef<HTMLUListElement>();
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();
+ 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<HTMLInputElement>, template: Template): void => {
@@ -89,9 +125,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
return (
<div className="templating-menu" >
<div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
- <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+ <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
{templateMenu}
- <button style={{ display: this._hidden ? "none" : "block" }} onClick={this.clearTemplates}>Clear</button>
+ <button onClick={this.toggleFloat}>Float</button>
+ <button onClick={this.clearTemplates}>Clear</button>
</ul>
</div>
);
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c595a4c56..cad87ebcc 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<string, CollectionViewType>([
+ ["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;
@@ -81,7 +99,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
var curPage = NumCast(this.props.Document.curPage, -1);
Doc.GetProto(doc).page = curPage;
- if (curPage >= 0) {
+ if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
Doc.GetProto(doc).annotationOn = this.props.Document;
}
allowDuplicates = true;
@@ -108,8 +126,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
- annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)
- );
+ annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
if (index !== -1) {
value.splice(index, 1);
@@ -147,7 +164,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
<div id="collectionBaseView"
style={{
pointerEvents: this.props.Document.isBackground ? "none" : "all",
- boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
+ boxShadow: this.props.Document.isBackground ? undefined : `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
}}
className={this.props.className || "collectionView-cont"}
onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 588102f01..77b698a07 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";
@@ -18,7 +18,6 @@ import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
-import { CollectionViewType } from './CollectionBaseView';
import "./CollectionDockingView.scss";
import { SubCollectionViewProps } from "./CollectionSubView";
import { ParentDocSelector } from './ParentDocumentSelector';
@@ -410,10 +409,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.reactComponents = [dragSpan, upDiv];
tab.element.append(dragSpan);
tab.element.append(upDiv);
- tab.reactionDisposer = reaction(() => [doc.title],
- () => {
- tab.titleElement[0].textContent = doc.title;
- }, { fireImmediately: true });
+ tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => {
+ tab.titleElement[0].textContent = doc.title, { fireImmediately: true };
+ tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegree(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegree(doc)]} 1px`;
+ });
//TODO why can't this just be doc instead of the id?
tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
}
@@ -421,9 +420,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.titleElement[0].Tab = tab;
tab.closeElement.off('click') //unbind the current click handler
.click(async function () {
- if (tab.reactionDisposer) {
- tab.reactionDisposer();
- }
+ tab.reactionDisposer && tab.reactionDisposer();
let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
if (doc instanceof Doc) {
let theDoc = doc;
@@ -511,7 +508,7 @@ interface DockedFrameProps {
}
@observer
export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- _mainCont = React.createRef<HTMLDivElement>();
+ _mainCont: HTMLDivElement | undefined = undefined;
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Doc>;
@@ -551,6 +548,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
private onActiveContentItemChanged() {
if (this.props.glContainer.tab) {
this._isActive = this.props.glContainer.tab.isActive;
+ !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
}
}
@@ -569,9 +567,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
ScreenToLocalTransform = () => {
- if (this._mainCont.current && this._mainCont.current.children) {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement);
- scale = Utils.GetScreenTransform(this._mainCont.current).scale;
+ if (this._mainCont && this._mainCont!.children) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
+ scale = Utils.GetScreenTransform(this._mainCont).scale;
return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
}
return Transform.Identity();
@@ -607,6 +605,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
focus={emptyFunction}
+ backgroundColor={returnEmptyString}
addDocTab={this.addDocTab}
ContainingCollectionView={undefined}
zoomToScale={emptyFunction}
@@ -615,7 +614,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
@computed get content() {
return (
- <div className="collectionDockingView-content" ref={this._mainCont}
+ <div className="collectionDockingView-content" ref={action((ref: HTMLDivElement) => {
+ this._mainCont = ref;
+ if (ref) {
+ this._panelWidth = Number(getComputedStyle(ref).width!.replace("px", ""));
+ this._panelHeight = Number(getComputedStyle(ref).height!.replace("px", ""));
+ }
+ })}
style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
{this.docView}
</div >);
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 70010819a..8eda4d9ee 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,66 +1,31 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { 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<FieldViewProps> {
- private _pdfBox?: PDFBox;
- private _reactionDisposer?: IReactionDisposer;
- private _buttonTray: React.RefObject<HTMLDivElement>;
-
- constructor(props: FieldViewProps) {
- super(props);
-
- this._buttonTray = 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();
- }
-
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();
+ private _pdfBox?: PDFBox;
+ private _buttonTray: React.RefObject<HTMLDivElement> = React.createRef();
- 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 (
<div className="collectionPdfView-buttonTray" ref={this._buttonTray} key="tray" style={{ height: "100%" }}>
- <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
- <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
- {/* <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
- <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} />
- </div> */}
+ <button className="collectionPdfView-backward" onClick={() => this._pdfBox && this._pdfBox.BackPage()}>{"<"}</button>
+ <button className="collectionPdfView-forward" onClick={() => this._pdfBox && this._pdfBox.ForwardPage()}>{">"}</button>
</div>
);
}
@@ -73,20 +38,16 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; };
-
- private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
- let props = { ...this.props, ...renderProps };
- return (
- <>
- <CollectionFreeFormView {...props} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} />
- {renderProps.active() ? this.uIButtons : (null)}
- </>
- );
+ subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ return (<>
+ <CollectionFreeFormView {...this.props} {...renderProps} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} />
+ {renderProps.active() ? this.uIButtons : (null)}
+ </>);
}
render() {
return (
- <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}>
+ <CollectionBaseView {...this.props} className={"collectionPdfView-cont"} onContextMenu={this.onContextMenu}>
{this.subView}
</CollectionBaseView>
);
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<CellProps> {
};
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 9efd0d3ec..897796174 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";
@@ -50,7 +50,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
["title", ColumnType.String],
["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number],
["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["libraryBrush", ColumnType.Boolean], ["zIndex", ColumnType.Number]
+ ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number]
]);
@observer
@@ -303,14 +303,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return resized;
}, [] as { "id": string, "value": number }[]);
}
-
- @computed get sorted(): { "id": string, "desc": boolean }[] {
+ @computed get sorted(): { id: string, desc: boolean }[] {
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: boolean }[]);
}
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@@ -974,14 +973,16 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
let input = this.props.previewScript === undefined ? (null) :
<div ref={this.createTarget}><input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange}
style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} /></div>;
- return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width(), height: "100%" }}>
+ return (<div className="collectionSchemaView-previewRegion"
+ style={{ width: this.props.width(), height: this.props.height() }}>
{!this.props.Document || !this.props.width ? (null) : (
<div className="collectionSchemaView-previewDoc"
style={{
transform: `translate(${this.centeringOffset}px, 0px)`,
borderRadius: this.borderRounding,
display: "inline",
- height: "100%"
+ height: this.props.height(),
+ width: this.props.width()
}}>
<DocumentView
DataDoc={this.props.DataDocument}
@@ -999,6 +1000,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
PanelHeight={this.PanelHeight}
ContainingCollectionView={this.props.CollectionView}
focus={emptyFunction}
+ backgroundColor={returnEmptyString}
parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 015955816..271ad2d58 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -9,6 +9,10 @@
overflow-y: auto;
flex-wrap: wrap;
transition: top .5s;
+ .collectionSchemaView-previewDoc {
+ height: 100%;
+ position: absolute;
+ }
.collectionStackingView-docView-container {
width: 45%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9741b9e89..b87be5d68 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,41 +45,38 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
}
+
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<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []]));
- if (sectionFilter) {
- this.filteredChildren.map(d => {
- let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `NO ${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;
- }
+ if (!this.sectionFilter) return new Map<SchemaHeaderField, Doc[]>();
- // look for if header exists already
- let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`));
- if (existingHeader) {
- fields.get(existingHeader)!.push(d);
- }
- else {
- let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`);
- fields.set(newSchemaHeader, [d]);
- sectionHeaders!.push(newSchemaHeader);
- }
- });
+ if (this.sectionHeaders === undefined) {
+ this.props.Document.sectionHeaders = new List<SchemaHeaderField>();
}
+ const sectionHeaders = this.sectionHeaders!;
+ let fields = new Map<SchemaHeaderField, Doc[]>(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);
+ }
+ });
return fields;
}
componentDidMount() {
// is there any reason this needs to exist? -syip
- this._heightDisposer = reaction(() => [this.yMargin, this.props.Document[WidthSym](), this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
+ this._heightDisposer = reaction(() => [this.props.Document.autoHeight, this.yMargin, this.props.Document[WidthSym](), this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
() => {
if (this.singleColumn && BoolCast(this.props.Document.autoHeight)) {
let hgt = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) => {
@@ -94,10 +90,8 @@ 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.props.Document.sectionHeaders = new List();
- }
+ () => this.sectionFilter,
+ () => this.props.Document.sectionHeaders = new List()
);
}
componentWillUnmount() {
@@ -184,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);
@@ -231,8 +225,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
});
}
+ headings = () => Array.from(this.Sections.keys());
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) {
@@ -243,7 +238,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return <CollectionStackingViewFieldColumn
key={heading ? heading.heading : ""}
cols={cols}
- headings={() => Array.from(this.Sections.keys())}
+ headings={this.headings}
heading={heading ? heading.heading : ""}
headingObject={heading}
docList={docList}
@@ -252,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;
}
@@ -274,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() {
@@ -294,14 +282,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return (
<div className="collectionStackingView"
ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => 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).
- 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.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')) ?
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
style={{ width: (this.columnWidth / (headings.length + ((this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0))) - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7f5d78313..24bd24d11 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -70,8 +70,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
+ 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() {
@@ -79,11 +80,17 @@ class TreeView extends React.Component<TreeViewProps> {
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
@@ -120,19 +127,19 @@ class TreeView extends React.Component<TreeViewProps> {
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
onPointerEnter = (e: React.PointerEvent): void => {
- this.props.active() && (this.props.document.libraryBrush = true);
+ this.props.active() && Doc.BrushDoc(this.dataDoc);
if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
this._header!.current!.className = "treeViewItem-header";
document.addEventListener("pointermove", this.onDragMove, true);
}
}
onPointerLeave = (e: React.PointerEvent): void => {
- this.props.document.libraryBrush = false;
+ Doc.UnBrushDoc(this.dataDoc);
this._header!.current!.className = "treeViewItem-header";
document.removeEventListener("pointermove", this.onDragMove, true);
}
onDragMove = (e: PointerEvent): void => {
- this.props.document.libraryBrush = false;
+ Doc.UnBrushDoc(this.dataDoc);
let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
@@ -276,13 +283,15 @@ class TreeView extends React.Component<TreeViewProps> {
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 <ul key={this.fieldKey + "more"}>
- {!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 <ul key={expandKey + "more"}>
+ {!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)}
</ul >;
@@ -332,9 +341,12 @@ class TreeView extends React.Component<TreeViewProps> {
let headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
- this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
- this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
- 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}
@@ -347,7 +359,7 @@ class TreeView extends React.Component<TreeViewProps> {
return <>
<div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
style={{
- background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0",
+ background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
}} >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 57dc5879b..7a402798e 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,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, faCopy);
@observer
export class CollectionView extends React.Component<FieldViewProps> {
@@ -95,7 +86,12 @@ export class CollectionView extends React.Component<FieldViewProps> {
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.usePivotLayout;
+ }, icon: "signature"
+ });
if (CollectionBaseView.InSafeMode()) {
ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
}
@@ -106,6 +102,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
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: () => this.props.Document.usePivotLayout = true });
break;
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 1b2561953..52c47e7e8 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<CollectionViewChro
}
}
+ private get document() {
+ return this.props.CollectionView.props.Document;
+ }
+
+ private get pivotKey() {
+ return StrCast(this.document.pivotField);
+ }
+
+ private set pivotKey(value: string) {
+ this.document.pivotField = value;
+ }
+
+ @observable private pivotKeyDisplay = this.pivotKey;
+ getPivotInput = () => {
+ if (!this.document.usePivotLayout) {
+ return (null);
+ }
+ return (<input className="collectionViewBaseChrome-viewSpecsInput"
+ placeholder="PIVOT ON..."
+ value={this.pivotKeyDisplay}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => this.pivotKeyDisplay = e.currentTarget.value)}
+ onKeyPress={action((e: React.KeyboardEvent<HTMLInputElement>) => {
+ let value = e.currentTarget.value;
+ if (e.which === 13) {
+ this.pivotKey = value;
+ this.pivotKeyDisplay = "";
+ }
+ })} />);
+ }
+
render() {
return (
<div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -70 : 0 }}>
@@ -219,6 +249,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
value={this.filterValue ? this.filterValue.script.originalScript : ""}
onChange={(e) => { }}
onPointerDown={this.openViewSpecs} />
+ {this.getPivotInput()}
<div className="collectionViewBaseChrome-viewSpecsMenu"
onPointerDown={this.openViewSpecs}
style={{
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 0beb0086b..e9791df4e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,15 +1,15 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faBrain } from "@fortawesome/free-solid-svg-icons";
-import { action, computed } from "mobx";
+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";
+import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils } from "../../../../Utils";
+import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
@@ -29,8 +29,8 @@ import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
import { pageSchema } from "../../nodes/ImageBox";
import { OverlayElementOptions, OverlayView } from "../../OverlayView";
import PDFMenu from "../../pdf/PDFMenu";
-import { ScriptBox } from "../../ScriptBox";
import { CollectionSubView } from "../CollectionSubView";
+import { ScriptBox } from "../../ScriptBox";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
@@ -41,7 +41,11 @@ import { ClientRecommender } from "../../../ClientRecommender";
import { SearchUtil } from "../../../util/SearchUtil";
import { SearchBox } from "../../SearchBox";
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain);
+import { DocumentType, Docs } from "../../../documents/Documents";
+import { RouteStore } from "../../../../server/RouteStore";
+import { string, number, elementType } from "prop-types";
+
+library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
export const panZoomSchema = createSchema({
panX: "number",
@@ -51,6 +55,139 @@ export const panZoomSchema = createSchema({
arrangeInit: ScriptField,
});
+export interface ViewDefBounds {
+ x: number;
+ y: number;
+ z?: number;
+ width: number;
+ height: number;
+}
+
+export interface ViewDefResult {
+ ele: JSX.Element;
+ bounds?: ViewDefBounds;
+}
+
+export namespace PivotView {
+
+ export interface PivotData {
+ type: string;
+ text: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ fontSize: number;
+ }
+
+ export const elements = (target: CollectionFreeFormView) => {
+ let collection = target.Document;
+ const field = StrCast(collection.pivotField) || "title";
+ const width = NumCast(collection.pivotWidth) || 200;
+
+ const groups = new Map<FieldResult<Field>, 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<Doc, ViewDefBounds>();
+ 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: (
+ <CollectionFreeFormDocumentView
+ key={doc[Id]}
+ x={pos.x}
+ y={pos.y}
+ width={pos.width}
+ height={pos.height}
+ {...target.getChildDocumentViewProps(doc)}
+ />),
+ 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]>;
const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema);
@@ -70,7 +207,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)
@@ -80,7 +217,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,
@@ -101,6 +238,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) => {
@@ -110,6 +248,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[]) => {
@@ -129,17 +268,38 @@ 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);
+ }
+ _clusterDistance = 75;
+ 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;
+ 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;
+ }
@undoBatch
@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 => {
@@ -155,6 +315,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
this.bringToFront(d);
});
+
+ this.updateClusters();
}
}
else if (de.data instanceof DragManager.AnnotationDragData) {
@@ -175,6 +337,87 @@ 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() {
+ this.sets.length = 0;
+ this.childDocs.map(c => {
+ let included = [];
+ for (let i = 0; i < this.sets.length; i++) {
+ for (let member of this.sets[i]) {
+ if (this.boundsOverlap(c, member)) {
+ included.push(i);
+ break;
+ }
+ }
+ }
+ if (included.length === 0) {
+ this.sets.push([c]);
+ } else if (included.length === 1) {
+ this.sets[included[0]].push(c);
+ } else {
+ this.sets[included[0]].push(c);
+ for (let s = 1; s < included.length; s++) {
+ this.sets[included[0]].push(...this.sets[included[s]]);
+ this.sets[included[s]].length = 0;
+ }
+ }
+ });
+ this.sets.map((set, i) => set.map(member => member.cluster = i));
+ }
+
+ getClusterColor = (doc: Doc) => {
+ if (this.props.Document.useClusters) {
+ let cluster = NumCast(doc.cluster);
+ 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];
+ 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 "";
+ }
+
@action
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
@@ -195,6 +438,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble) {
+ 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);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ return;
+ }
let x = this.Document.panX || 0;
let y = this.Document.panY || 0;
let docs = this.childDocs || [];
@@ -242,12 +492,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.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
+ e.stopPropagation();
return;
}
+
let childSelected = this.childDocs.some(doc => {
var dv = DocumentManager.Instance.getDocumentView(doc);
return dv && SelectionManager.IsSelected(dv) ? true : false;
@@ -256,21 +505,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.scrollHeight === 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();
@@ -282,23 +530,19 @@ 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;
- // 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]();
+ 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;
}
}
@@ -312,7 +556,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
bringToFront = (doc: Doc, sendToBack?: boolean) => {
- if (sendToBack) {
+ if (sendToBack || doc.isBackground) {
doc.zIndex = 0;
return;
}
@@ -382,16 +626,9 @@ 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 {
- let self = this;
let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
return {
DataDoc: pair.data,
@@ -399,7 +636,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],
@@ -407,6 +644,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,
@@ -430,6 +668,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,
@@ -439,19 +678,33 @@ 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;
+ }
+
+ viewDefsToJSX = (views: any[]) => {
+ let elements: ViewDefResult[] = [];
+ if (Array.isArray(views)) {
+ elements = views.reduce<typeof elements>((prev, ele) => {
+ const jsx = this.viewDefToJSX(ele);
+ jsx && prev.push(jsx);
+ return prev;
+ }, elements);
+ }
+ return elements;
}
- private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined {
+ private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
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");
@@ -463,7 +716,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
ele: <div className="collectionFreeform-customText" style={{
transform: `translate(${x}px, ${y}px)`,
width, height, fontSize
- }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! }
+ }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
};
}
}
@@ -475,20 +728,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, 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<typeof elements>((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) => {
@@ -497,28 +744,36 @@ 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: <CollectionFreeFormDocumentView key={doc[Id]}
x={script ? pos.x : undefined} y={script ? pos.y : undefined}
width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
- 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) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
});
}
}
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.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() {
+ return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+ }
+
@action
onCursorMove = (e: React.PointerEvent) => {
@@ -526,50 +781,82 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@action
+ 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.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData);
+ }
+
onContextMenu = (e: React.MouseEvent) => {
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",
+ 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 () => {
- 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");
- }
+ 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 ? "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 ? "chalkboard" : "chalkboard"
+ });
+ layoutItems.push({
+ description: "Arrange contents in grid",
+ 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"
});
ContextMenu.Instance.addItem({
description: "Import document", icon: "upload", event: () => {
@@ -624,6 +911,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
]
+ private overlayChildViews = () => {
+ return [...this.overlayViews];
+ }
public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
return () => {
@@ -672,6 +962,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
</CollectionFreeFormViewPannableContents>
</MarqueeView>
+ {this.overlayChildViews()}
<CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b9ee588dd..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<MarqueeViewProps>
}
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<MarqueeViewProps>
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<MarqueeViewProps>
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);
@@ -278,11 +280,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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",
});
- 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") {
@@ -366,7 +370,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
}
- marqueeSelect() {
+ marqueeSelect(selectBackgrounds: boolean = true) {
let selRect = this.Bounds;
let selection: Doc[] = [];
this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
@@ -378,7 +382,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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/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/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ffd760e0..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<CollectionFreeF
return undefined;
}
+ @computed
+ get clusterColor() { return this.props.backgroundColor(this.props.Document); }
+
+ clusterColorFunc = (doc: Doc) => this.clusterColor;
+
render() {
const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
return (
@@ -77,6 +83,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
transformOrigin: "left top",
position: "absolute",
backgroundColor: "transparent",
+ boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
+ this.clusterColor ? (
+ this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
+ `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
borderRadius: this.borderRounding(),
transform: this.transform,
transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
@@ -87,6 +97,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
<DocumentView {...this.props}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
+ backgroundColor={this.clusterColorFunc}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
animateBetweenIcon={this.animateBetweenIcon}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d4adcfca1..c8eab85c2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,19 +1,23 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import * as rp from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { List } from "../../../new_fields/List";
import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
+import { RouteStore } from '../../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentType } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ClientUtils } from '../../util/ClientUtils';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, dropActionType } from "../../util/DragManager";
-import { SearchUtil } from "../../util/SearchUtil";
+import { LinkManager } from '../../util/LinkManager';
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -22,27 +26,20 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from "../DocComponent";
+import { EditableView } from '../EditableView';
+import { OverlayView } from '../OverlayView';
import { PresentationView } from "../presentationview/PresentationView";
-import { Template, Templates } from "./../Templates";
+import { ScriptingRepl } from '../ScriptingRepl';
+import { Template } from "./../Templates";
import { DocumentContentsView } from "./DocumentContentsView";
-import * as rp from "request-promise";
import "./DocumentView.scss";
-import React = require("react");
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { list, object, createSimpleSchema } from 'serializr';
-import { LinkManager } from '../../util/LinkManager';
-import { RouteStore } from '../../../server/RouteStore';
import { FormattedTextBox } from './FormattedTextBox';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
-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 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);
@@ -100,6 +97,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 | undefined;
getScale: () => number;
animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
ChromeHeight?: () => number;
@@ -121,6 +119,7 @@ export const positionSchema = createSchema({
height: "number",
x: "number",
y: "number",
+ z: "number",
});
export type PositionDocument = makeInterface<[typeof positionSchema]>;
@@ -152,10 +151,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
set templates(templates: List<string>) { this.props.Document.templates = templates; }
screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
- constructor(props: DocumentViewProps) {
- super(props);
- }
-
_animateToIconDisposer?: IReactionDisposer;
_reactionDisposer?: IReactionDisposer;
@action
@@ -306,7 +301,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
fullScreenAlias.showCaption = true;
this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
SelectionManager.DeselectAll();
- this.props.Document.libraryBrush = false;
+ Doc.UnBrushDoc(this.props.Document);
}
else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
@@ -364,12 +359,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!linkedFwdDocs.some(l => l instanceof Promise)) {
let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
- DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => {
- this.props.focus(this.props.Document, true, 1);
- setTimeout(() =>
- this.props.addDocTab(document, undefined, maxLocation), 1000);
- }
- , linkedFwdPage[altKey ? 1 : 0], targetContext);
+ DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
+ document => { // open up target if it's not already in view ...
+ this.props.focus(this.props.Document, true, 1); // by zooming into the button document first
+ setTimeout(() => this.props.addDocTab(document, undefined, maxLocation), 1000); // then after the 1sec animation, open up the target in a new tab
+ },
+ linkedFwdPage[altKey ? 1 : 0], targetContext);
}
}
}
@@ -414,7 +409,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(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");
+ }
@undoBatch
makeBtnClicked = (): void => {
@@ -447,15 +445,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let targetDoc = this.props.Document;
targetDoc.targetContext = de.data.targetContext;
let annotations = await DocListCastAsync(annotationDoc.annotations);
- if (annotations) {
- annotations.forEach(anno => {
- anno.target = targetDoc;
- });
- }
- let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
- if (pdfDoc) {
- DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
- }
+ annotations && annotations.forEach(anno => anno.target = targetDoc);
+
+ DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
}
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
@@ -540,8 +532,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
listen = async () => {
- let transcript = await DictationManager.Instance.listen();
- transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
+ Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({
+ continuous: { indefinite: true },
+ interimHandler: (results: string) => {
+ let main = MainView.Instance;
+ main.dictationSuccess = true;
+ main.dictatedPhrase = results;
+ main.isListening = { interim: true };
+ }
+ });
}
@action
@@ -650,8 +649,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
- onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
- onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
+ onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); };
+ onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); };
isSelected = () => SelectionManager.IsSelected(this);
@action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
@@ -679,12 +678,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(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.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%";
@@ -694,27 +692,28 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(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("<FormattedTextBox") ? showTitle : undefined;
+ let brushDegree = Doc.IsBrushedDegree(this.layoutDoc);
return (
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
color: foregroundColor,
- outlineColor: "maroon",
- outlineStyle: "dashed",
- 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) ?
- `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
- marginTop: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
- `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
- border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
- `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
+ outlineStyle: ["none", "dashed", "solid"][brushDegree],
+ outlineWidth: brushDegree && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${brushDegree * this.props.ScreenToLocalTransform().Scale}px` : "0px",
+ marginLeft: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ marginTop: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ border: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
borderRadius: "inherit",
background: backgroundColor,
width: nativeWidth,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index e076efe18..44b5d2c21 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -180,14 +180,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
this._applyingChange = true;
const fieldkey = "preview";
- if (Object.keys(this.dataDoc).indexOf(fieldkey) !== -1) {
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
- this.dataDoc[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
- }
- else {
- Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
- Doc.GetProto(this.dataDoc)[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
- }
if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
@@ -674,16 +666,17 @@ 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 (
<div className={`formattedTextBox-cont-${style}`} ref={this._ref}
style={{
height: this.props.height ? this.props.height : undefined,
background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined,
- opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
+ opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
- pointerEvents: interactive ? "all" : "none",
+ pointerEvents: interactive,
fontSize: "13px"
}}
onKeyDown={this.onKeyPress}
@@ -694,12 +687,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
onMouseDown={this.onMouseDown}
- // tfs: do we need this event handler
onWheel={this.onPointerWheel}
onPointerEnter={this.onPointerEnter}
onPointerLeave={this.onPointerLeave}
>
- <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} />
+ <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap" }} />
</div>
);
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 660772c0e..73b892e26 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,44 +1,39 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faImage, faFileAudio, faPaintBrush, faAsterisk, faBrain } 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, faBrain } 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';
-//import { Recommender } from '../../../server/Recommender';
-import requestPromise = require('request-promise');
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
const { Howl } = require('howler');
-library.add(faImage, faEye, faPaintBrush, faBrain);
+library.add(faImage, faEye as any, faPaintBrush, faBrain);
library.add(faFileAudio, faAsterisk);
@@ -240,7 +235,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(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);
+ }
}
extractText = () => {
@@ -265,7 +262,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(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
@@ -326,14 +325,14 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(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/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index f10079169..0d4b377dd 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -73,6 +73,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript): boolean {
const { script, type, onDelegate } = kvpScript;
+ //const target = onDelegate ? (doc.layout instanceof Doc ? doc.layout : doc) : Doc.GetProto(doc); // bcz: need to be able to set fields on layout templates
const target = onDelegate ? doc : Doc.GetProto(doc);
let field: Field;
if (type === "computed") {
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
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 1d4fcad69..a119eb39b 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -6,7 +6,7 @@ import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
import './LinkMenu.scss';
import React = require("react");
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, DocListCastAsync } from '../../../new_fields/Doc';
import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
import { observable, action } from 'mobx';
import { LinkManager } from '../../util/LinkManager';
@@ -52,7 +52,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(targetContext!));
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
}
else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index e7655d598..c88a94c28 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,37 +1,3 @@
-.react-pdf__Page {
- transform-origin: left top;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.react-pdf__Page__textContent span {
- user-select: text;
-}
-
-.react-pdf__Document {
- position: absolute;
-}
-
-.pdfBox-buttonTray {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 25;
- pointer-events: all;
-}
-
-.pdfBox-thumbnail {
- position: absolute;
- width: 100%;
-}
-
-.pdfButton {
- pointer-events: all;
- width: 100px;
- height: 100px;
-}
-
.pdfBox-cont,
.pdfBox-cont-interactive {
display: flex;
@@ -39,30 +5,24 @@
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
+ .pdfBox-scrollHack {
+ pointer-events: none;
+ }
}
.pdfBox-cont {
pointer-events: none;
-
- .textlayer {
- pointer-events: none;
-
+ .pdfPage-textlayer {
span {
pointer-events: none !important;
+ user-select: none;
}
}
-
- .page-cont {
- pointer-events: none;
- }
}
.pdfBox-cont-interactive {
pointer-events: all;
- display: flex;
- flex-direction: row;
-
- .textlayer {
+ .pdfPage-textlayer {
span {
pointer-events: all !important;
user-select: text;
@@ -70,11 +30,22 @@
}
}
-.pdfBox-contentContainer {
- position: absolute;
+.react-pdf__Page {
transform-origin: left top;
+ position: absolute;
+ top: 0;
+ left: 0;
}
+.react-pdf__Page__textContent span {
+ user-select: text;
+}
+
+.react-pdf__Document {
+ position: absolute;
+}
+
+
.pdfBox-settingsCont {
position: absolute;
right: 0;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fa072aecf..6450cb826 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,34 @@ export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === K
export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(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<Pdfjs.PDFDocumentProxy>;
+
+ @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"); }
- @observable private _flyout: boolean = false;
- private _mainCont: React.RefObject<HTMLDivElement>;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _reactionDisposer?: IReactionDisposer;
private _keyValue: string = "";
private _valueValue: string = "";
private _scriptValue: string = "";
- private _keyRef: React.RefObject<HTMLInputElement>;
- private _valueRef: React.RefObject<HTMLInputElement>;
- private _scriptRef: React.RefObject<HTMLInputElement>;
+ private _keyRef: React.RefObject<HTMLInputElement> = React.createRef();
+ private _valueRef: React.RefObject<HTMLInputElement> = React.createRef();
+ private _scriptRef: React.RefObject<HTMLInputElement> = 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.props.Document.panY,
+ () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.panY), behavior: "auto" })
);
-
- this._keyRef = React.createRef();
- this._valueRef = React.createRef();
- this._scriptRef = React.createRef();
- }
-
- componentDidMount() {
- if (this.props.setPdfBox) this.props.setPdfBox(this);
}
componentWillUnmount() {
@@ -76,186 +63,144 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
public GetPage() {
- return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 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.pdfHeight)) + 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.pdfHeight);
+ this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
}
}
+
+ @action
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.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight);
}
}
+ @action
public ForwardPage() {
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.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
}
}
- private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._keyValue = e.currentTarget.value;
- }
-
- private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._valueValue = e.currentTarget.value;
- }
-
@action
- private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._scriptValue = e.currentTarget.value;
+ setPanY = (y: number) => {
+ this.containingCollectionDocument && (this.containingCollectionDocument.panY = y);
}
+ @action
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);
- }
+ script.compiled && (this.props.Document.filterScript = new ScriptField(script));
}
- @action
- private toggleFlyout = () => {
- this._flyout = !this._flyout;
+ scrollTo = (y: number) => {
+ this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
}
- @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) {
- if (this._mainCont.current) {
- this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current!.offsetHeight / 2), 0), behavior: "auto" });
- }
- }
+ private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => this._keyValue = e.currentTarget.value;
+ private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => this._valueValue = e.currentTarget.value;
+ private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => this._scriptValue = e.currentTarget.value;
settingsPanel() {
return !this.props.active() ? (null) :
- (
- <div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>
- <button className="pdfBox-settingsButton" onClick={this.toggleFlyout} title="Open Annotation Settings"
- style={{ marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` }}>
- <div className="pdfBox-settingsButton-arrow"
- style={{
- borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
- borderBottom: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
- borderRight: `25px solid ${this._flyout ? "transparent" : "#121721"}`,
- transform: `scaleX(${this._flyout ? -1 : 1})`
- }}></div>
- <div className="pdfBox-settingsButton-iconCont">
- <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="3x" />
- </div>
- </button>
- <div className="pdfBox-settingsFlyout" style={{ left: `${this._flyout ? -600 : 100}px` }} >
- <div className="pdfBox-settingsFlyout-title">
- Annotation View Settings
- </div>
- <div className="pdfBox-settingsFlyout-kvpInput">
- <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange}
- style={{ gridColumn: 1 }} ref={this._keyRef} />
- <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange}
- style={{ gridColumn: 3 }} ref={this._valueRef} />
- </div>
- <div className="pdfBox-settingsFlyout-kvpInput">
- <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />
- </div>
- <div className="pdfBox-settingsFlyout-kvpInput">
- <button style={{ gridColumn: 1 }} onClick={this.resetFilters}>
- <FontAwesomeIcon style={{ color: "white" }} icon="trash" size="lg" />
- &nbsp; Reset Filters
- </button>
- <button style={{ gridColumn: 3 }} onClick={this.applyFilter}>
- <FontAwesomeIcon style={{ color: "white" }} icon="check" size="lg" />
- &nbsp; Apply
- </button>
- </div>
+ (<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>
+ <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings"
+ style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}>
+ <div className="pdfBox-settingsButton-arrow"
+ style={{
+ borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
+ borderBottom: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
+ borderRight: `25px solid ${this._flyout ? "transparent" : "#121721"}`,
+ transform: `scaleX(${this._flyout ? -1 : 1})`
+ }} />
+ <div className="pdfBox-settingsButton-iconCont">
+ <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="3x" />
+ </div>
+ </button>
+ <div className="pdfBox-settingsFlyout" style={{ left: `${this._flyout ? -600 : 100}px` }} >
+ <div className="pdfBox-settingsFlyout-title">
+ Annotation View Settings
+ </div>
+ <div className="pdfBox-settingsFlyout-kvpInput">
+ <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange}
+ style={{ gridColumn: 1 }} ref={this._keyRef} />
+ <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange}
+ style={{ gridColumn: 3 }} ref={this._valueRef} />
+ </div>
+ <div className="pdfBox-settingsFlyout-kvpInput">
+ <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />
+ </div>
+ <div className="pdfBox-settingsFlyout-kvpInput">
+ <button style={{ gridColumn: 1 }} onClick={this.resetFilters}>
+ <FontAwesomeIcon style={{ color: "white" }} icon="trash" size="lg" />
+ &nbsp; Reset Filters
+ </button>
+ <button style={{ gridColumn: 3 }} onClick={this.applyFilter}>
+ <FontAwesomeIcon style={{ color: "white" }} icon="check" size="lg" />
+ &nbsp; Apply
+ </button>
</div>
</div>
- );
+ </div>);
}
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 || !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.dataDoc.height = this.dataDoc[WidthSym]() * (nh / nw);
+ this.dataDoc.scrollHeight = np * this.dataDoc.nativeHeight;
}
}
@action
onScroll = (e: React.UIEvent<HTMLDivElement>) => {
-
- 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.panY = 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 <div>{`pdf, ${this.props.Document.data}, not found`}</div>;
let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
- return (
+ return (!(pdfUrl instanceof PdfField) || !this._pdf ?
+ <div>{`pdf, ${this.props.Document.data}, not found`}</div> :
<div className={classname}
onScroll={this.onScroll}
- style={{
- marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px`
- }}
- ref={this._mainCont}
- onWheel={(e: React.WheelEvent) => {
- e.stopPropagation();
- }}>
- <PDFViewer url={pdfUrl.url.pathname} loaded={this.loaded} scrollY={this._scrollY} parent={this} />
- {/* <div style={{ width: "100px", height: "300px" }}></div> */}
+ style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}
+ ref={this._mainCont}>
+ <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + (NumCast(this.props.Document.nativeHeight) - NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.scale, 1)), width: "100%" }} />
+ <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)}
+ Document={this.props.Document} DataDoc={this.props.DataDoc}
+ addDocTab={this.props.addDocTab} setPanY={this.setPanY}
+ addDocument={this.props.addDocument}
+ fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} />
{this.settingsPanel()}
- </div>
- );
+ </div>);
}
-
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 162ac1d98..c8749b7cd 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -8,6 +8,7 @@ import "./WebBox.scss";
import React = require("react");
import { InkTool } from "../../../new_fields/InkField";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
@observer
export class WebBox extends React.Component<FieldViewProps> {
@@ -52,7 +53,7 @@ export class WebBox extends React.Component<FieldViewProps> {
if (field instanceof HtmlField) {
view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ view = <iframe src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%" }} />;
} else {
view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
}
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss
new file mode 100644
index 000000000..0c6df74f0
--- /dev/null
+++ b/src/client/views/pdf/Annotation.scss
@@ -0,0 +1,7 @@
+.pdfAnnotation {
+ pointer-events: all;
+ user-select: none;
+ position: absolute;
+ 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 513f9fed6..7ba7b6d14 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,38 +1,29 @@
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 { 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 { 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<IAnnotationProps> {
render() {
- let annotationDocs = DocListCast(this.props.anno.annotations);
- let res = annotationDocs.map(a => {
- let type = NumCast(a.type);
- switch (type) {
- // case AnnotationTypes.Pin:
- // return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
- case AnnotationTypes.Region:
- return <RegionAnnotation parent={this.props.parent} document={a} index={this.props.index} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
- default:
- return <div></div>;
- }
- });
- return res;
+ return DocListCast(this.props.anno.annotations).map(a => (
+ <RegionAnnotation {...this.props} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />));
}
}
@@ -42,44 +33,29 @@ 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;
}
@observer
class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
- @observable private _backgroundColor: string = "red";
-
private _reactionDisposer?: IReactionDisposer;
private _scrollDisposer?: IReactionDisposer;
- private _mainCont: React.RefObject<HTMLDivElement>;
-
- constructor(props: IRegionAnnotationProps) {
- super(props);
-
- this._mainCont = React.createRef();
- }
+ private _mainCont: React.RefObject<HTMLDivElement> = 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,
+ (del) => del && 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(),
+ (ind) => ind === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale)
);
}
@@ -89,16 +65,15 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
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<Doc>(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<Doc>(newAnnotations);
+ }
+
+ DocListCast(group.annotations).forEach(anno => anno.delete = true);
}
PDFMenu.Instance.fadeOut(true);
@@ -106,9 +81,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
pinToPres = () => {
let group = FieldValue(Cast(this.props.document.group, Doc));
- if (group) {
- PresentationView.Instance.PinDoc(group);
- }
+ group && PresentationView.Instance.PinDoc(group);
}
@action
@@ -118,8 +91,9 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
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.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")),
+ undefined, undefined);
}
}
}
@@ -144,16 +118,13 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
render() {
- return (
- <div className="pdfViewer-annotationBox" onPointerDown={this.onPointerDown} ref={this._mainCont}
- style={{
- top: this.props.y * scale,
- 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)
- }}></div>
- );
+ return (<div className="pdfAnnotation" onPointerDown={this.onPointerDown} ref={this._mainCont}
+ style={{
+ top: this.props.y,
+ left: this.props.x,
+ width: this.props.width,
+ height: this.props.height,
+ 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/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 (
- <div className="pdfAnnotationLayer-cont" style={{ width: "100%", height: "100%", position: "relative", top: "-200%" }} onPointerDown={this.onPointerDown}>
-
- </div>
- );
+ return <div className="pdfAnnotationLayer-cont" onPointerDown={this.onPointerDown} />;
}
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 27c2a8f1a..3ed81faef 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<HTMLDivElement> = React.createRef();
+ private _commentCont = React.createRef<HTMLButtonElement>();
+ private _snippetButton: React.RefObject<HTMLButtonElement> = 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) => 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<HTMLDivElement> = React.createRef();
- private _commentCont = React.createRef<HTMLButtonElement>();
- private _snippetButton: React.RefObject<HTMLButtonElement> = 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" ? [
- <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked}
- style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
- </button>,
- <button className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>,
- this.Status === "snippet" ? <button className="pdfMenu-button" title="Drag to Snippetize Selection" onPointerDown={this.snippetStart} ref={this._snippetButton}><FontAwesomeIcon icon="cut" size="lg" /></button> : undefined,
- <button key="3" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin}
- style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
- </button>
- ] : [
- <button key="4" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>,
- <button key="5" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>,
- <div className="pdfMenu-addTag" key="3">
+ let buttons = this.Status === "pdf" || this.Status === "snippet" ?
+ [
+ <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>,
+ <button key="2" className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
+ <FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
+ <button key="3" className="pdfMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}>
+ <FontAwesomeIcon icon="cut" size="lg" /></button>,
+ <button key="4" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button>
+ ] : [
+ <button key="5" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
+ <button key="6" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}>
+ <FontAwesomeIcon icon="map-pin" size="lg" /></button>,
+ <div key="7" className="pdfMenu-addTag" >
<input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
<input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
</div>,
- <button key="6" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>,
+ <button key="8" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
+ <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
];
return (
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 0fde764d0..a2f3911c5 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,131 +1,93 @@
-.textLayer {
- div {
- user-select: text;
- }
-}
-.viewer-button-cont {
- position: absolute;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
-}
-
-.viewer-previousPage,
-.viewer-nextPage {
- background: grey;
- font-weight: bold;
- opacity: 0.5;
- padding: 0 10px;
- border-radius: 5px;
-}
-
-.textLayer {
- user-select: auto;
-}
-.viewer {
- // position: absolute;
- // top: 0;
-}
-
-.pdfViewer-text {
-
- .page {
- .canvasWrapper {
- display: none;
- }
-
- .textLayer {
- position: relative;
- user-select: none;
+.pdfViewer-viewer {
+ pointer-events:inherit;
+ width: 100%;
+ .pdfViewer-visibleElements {
+ .pdfPage-cont {
+ .pdfPage-textLayer {
+ div {
+ user-select: text;
+ }
+ span {
+ color: transparent;
+ position: absolute;
+ white-space: pre;
+ cursor: text;
+ -webkit-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+ }
+ }
}
}
-}
-.pdfViewer-viewerCont {
- width:100%;
-}
-
-.page-cont {
- .textLayer {
- user-select: auto;
-
- div {
- user-select: text;
- }
+ .pdfViewer-text {
+ transform: scale(1.5);
+ transform-origin: top left;
}
-}
-.pdfViewer-overlayCont {
- position: absolute;
- width: 100%;
- height: 100px;
- background: #121721;
- bottom: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 20px;
- overflow: hidden;
- transition: left .5s;
-}
-
-.pdfViewer-overlaySearchBar {
- width: 20%;
- height: 100%;
- font-size: 30px;
- padding: 5px;
-}
-
-.pdfViewer-overlayButton {
- border-bottom-left-radius: 50%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- height: 70px;
- background: none;
- padding: 0;
- position: absolute;
-
- .pdfViewer-overlayButton-arrow {
- width: 0;
- height: 0;
- border-top: 25px solid transparent;
- border-bottom: 25px solid transparent;
- border-right: 25px solid #121721;
- transition: all 0.5s;
+ .pdfViewer-annotationLayer {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ pointer-events: none;
+ .pdfPage-annotationBox {
+ position: absolute;
+ background-color: red;
+ opacity: 0.1;
+ }
}
- .pdfViewer-overlayButton-iconCont {
+ .pdfViewer-overlayCont {
+ position: absolute;
+ width: 100%;
+ height: 100px;
background: #121721;
- height: 50px;
- width: 70px;
+ bottom: 0;
display: flex;
justify-content: center;
align-items: center;
- margin-left: -2px;
- border-radius: 3px;
+ padding: 20px;
+ overflow: hidden;
+ transition: left .5s;
+ .pdfViewer-overlaySearchBar {
+ width: 20%;
+ height: 100%;
+ font-size: 30px;
+ padding: 5px;
+ }
}
-}
-.pdfViewer-overlayButton:hover {
- background: none;
-}
+ .pdfViewer-overlayButton {
+ border-bottom-left-radius: 50%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 70px;
+ background: none;
+ padding: 0;
+ position: absolute;
+
+ .pdfViewer-overlayButton-arrow {
+ width: 0;
+ height: 0;
+ border-top: 25px solid transparent;
+ border-bottom: 25px solid transparent;
+ border-right: 25px solid #121721;
+ transition: all 0.5s;
+ }
-.pdfViewer-annotationBox {
- position: absolute;
- background-color: red;
- opacity: 0.1;
-}
+ .pdfViewer-overlayButton-iconCont {
+ background: #121721;
+ height: 50px;
+ width: 70px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-left: -2px;
+ border-radius: 3px;
+ }
+ }
-.pdfViewer-annotationLayer {
- position: absolute;
- top: 0;
+ .pdfViewer-overlayButton:hover {
+ background: none;
+ }
}
-
-
-
-.pdfViewer-pinAnnotation {
- background-color: red;
- position: absolute;
- border-radius: 100%;
-} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5eb02a6da..08674720d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,167 +1,114 @@
+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, FieldResult } 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 { Utils, numberRange } 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 { CompileScript, CompiledScript } 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<IPDFViewerProps> {
- @observable _pdf: Opt<Pdfjs.PDFDocumentProxy>;
- private _mainDiv = React.createRef<HTMLDivElement>();
-
- @action
- componentDidMount() {
- Pdfjs.getDocument(this.props.url).promise.then(pdf => runInAction(() => this._pdf = pdf));
- }
-
- render() {
- return (
- <div className="pdfViewer-viewerCont" ref={this._mainDiv}>
- {!this._pdf ? (null) :
- <Viewer pdf={this._pdf} loaded={this.props.loaded} scrollY={this.props.scrollY} parent={this.props.parent} mainCont={this._mainDiv} url={this.props.url} />}
- </div>
- );
- }
-}
interface IViewerProps {
pdf: Pdfjs.PDFDocumentProxy;
- loaded: (nw: number, nh: number, np: number) => void;
- scrollY: number;
- parent: PDFBox;
- mainCont: React.RefObject<HTMLDivElement>;
url: string;
+ Document: Doc;
+ DataDoc?: Doc;
+ fieldExtensionDoc: Doc;
+ fieldKey: string;
+ loaded: (nw: number, nh: number, np: number) => void;
+ panY: number;
+ scrollTo: (y: number) => void;
+ active: () => boolean;
+ setPanY?: (n: number) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
}
/**
* Handles rendering and virtualization of the pdf
*/
@observer
-export class Viewer extends React.Component<IViewerProps> {
- // _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<IViewerProps> {
+ @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<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
- @observable private _script: CompileResult | undefined;
+ @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
@observable private _searching: boolean = false;
-
- @observable public Index: number = -1;
+ @observable private Index: number = -1;
private _pageBuffer: number = 1;
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _reactionDisposer?: IReactionDisposer;
private _annotationReactionDisposer?: IReactionDisposer;
- private _dropDisposer?: DragManager.DragDropDisposer;
private _filterReactionDisposer?: IReactionDisposer;
- private _viewer: React.RefObject<HTMLDivElement>;
- private _mainCont: React.RefObject<HTMLDivElement>;
+ private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _pdfViewer: any;
- // private _textContent: Pdfjs.TextContent[] = [];
private _pdfFindController: any;
private _searchString: string = "";
private _selectionText: string = "";
- constructor(props: IViewerProps) {
- super(props);
+ @computed get panY(): number { return this.props.panY; }
- 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();
- }
+ // startIndex: where to start rendering pages
+ @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.panY) - this._pageBuffer); }
- setSelectionText = (text: string) => {
- this._selectionText = text;
+ // endIndex: where to end rendering pages
+ @computed get endIndex(): number {
+ return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer);
}
- componentDidUpdate = (prevProps: IViewerProps) => {
- if (this.scrollY !== prevProps.scrollY) {
- this.renderPages();
- }
+ @computed get filteredAnnotations() {
+ return this._annotations.filter(anno => {
+ let run = this._script.run({ this: anno });
+ return run.success ? run.result : true;
+ });
}
- @action
- componentDidMount = () => {
- this._reactionDisposer = reaction(
+ componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages();
- () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0],
- async () => {
- await this.initialLoad();
- this.renderPages();
- }, { fireImmediately: true });
+ componentDidMount = async () => {
+ await this.initialLoad();
- 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._reactionDisposer = reaction(
+ () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0],
+ () => this.renderPages(),
{ fireImmediately: true });
+ this._annotationReactionDisposer = reaction(
+ () => 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.Index = -1;
- });
- }
- );
- }
-
- if (this._mainCont.current) {
- this._dropDisposer = this._mainCont.current && DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
- }
+ this._filterReactionDisposer = reaction(
+ () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }),
+ action(({ scriptField, annos }: { scriptField: FieldResult<ScriptField>, annos: Doc[] }) => {
+ this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
+ annos.forEach(d => {
+ let run = this._script.run(d);
+ d.opacity = !run.success || run.result ? 1 : 0;
+ });
+ this.Index = -1;
+ }),
+ { fireImmediately: true }
+ );
document.removeEventListener("copy", this.copy);
document.addEventListener("copy", this.copy);
@@ -171,162 +118,115 @@ export class Viewer extends React.Component<IViewerProps> {
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()) {
- 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();
- }
+ copy = (e: ClipboardEvent) => {
+ 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, "#0390fc")[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), "#0390fc", false)));
}
}
- scrollTo(y: number) {
- if (this.props.mainCont.current) {
- this.props.parent.scrollTo(y);
- }
- }
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
+
+ pageLoaded = (page: Pdfjs.PDFPageViewport): void => this.props.loaded(page.width, page.height, this.props.pdf.numPages);
+
+ setSelectionText = (text: string) => this._selectionText = text;
+
+ getIndex = () => this.Index;
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
- let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
this._isPage = Array<string>(this.props.pdf.numPages);
- // this._textContent = Array<Pdfjs.TextContent>(this.props.pdf.numPages);
- const proms: Pdfjs.PDFPromise<any>[] = [];
- 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<Pdfjs.PDFPromise<any>>((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, NumCast(this.props.Document.panY));
+ this.props.setPanY && this.props.setPanY(startY);
}
}
@action
- makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string, createLink: boolean = true): Doc => {
- let annoDocs: Doc[] = [];
+ makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): 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;
+ let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
+ let annoDocs: Doc[] = [];
let minY = Number.MAX_VALUE;
- this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => {
- for (let anno of value) {
+ 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.isButton = true;
+ anno.remove();
+ 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) / scale;
- if (anno.style.top) {
- annoDoc.y = parseInt(anno.style.top) / scale;
- minY = Math.min(parseInt(anno.style.top), minY);
- }
- 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 (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));
+ }));
- mainAnnoDoc.y = Math.max(minY, 0);
- mainAnnoDoc.annotations = new List<Doc>(annoDocs);
+ mainAnnoDocProto.y = Math.max(minY, 0);
+ mainAnnoDocProto.annotations = new List<Doc>(annoDocs);
+ }
+ mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title);
+ mainAnnoDocProto.annotationOn = this.props.Document;
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, mainAnnoDocProto, undefined, `Annotation from ${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<Doc>([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 => {
- this.props.loaded(page.width, page.height, this.props.pdf.numPages);
- }
-
@action
getPlaceholderPage = (page: number) => {
if (this._isPage[page] !== "none") {
this._isPage[page] = "none";
this._visibleElements[page] = (
<div key={`${this.props.url}-placeholder-${page + 1}`} className="pdfviewer-placeholder"
- style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }} />
- );
+ style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }}>
+ "PAGE IS LOADING... "
+ </div>);
}
}
@@ -334,25 +234,19 @@ export class Viewer extends React.Component<IViewerProps> {
getRenderedPage = (page: number) => {
if (this._isPage[page] !== "page") {
this._isPage[page] = "page";
- this._visibleElements[page] = (
- <Page
- setSelectionText={this.setSelectionText}
- size={this._pageSizes[page]}
- pdf={this.props.pdf}
- page={page}
- numPages={this.props.pdf.numPages}
- key={`${this.props.url}-rendered-${page + 1}`}
- name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
- pageLoaded={this.pageLoaded}
- parent={this.props.parent}
- makePin={emptyFunction}
- renderAnnotations={this.renderAnnotations}
- createAnnotation={this.createAnnotation}
- sendAnnotations={this.receiveAnnotations}
- makeAnnotationDocuments={this.makeAnnotationDocument}
- getScrollFromPage={this.getScrollFromPage}
- {...this.props} />
- );
+ this._visibleElements[page] = (<Page {...this.props}
+ size={this._pageSizes[page]}
+ numPages={this.props.pdf.numPages}
+ setSelectionText={this.setSelectionText}
+ page={page}
+ key={`${this.props.url}-rendered-${page + 1}`}
+ name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
+ pageLoaded={this.pageLoaded}
+ renderAnnotations={this.renderAnnotations}
+ createAnnotation={this.createAnnotation}
+ sendAnnotations={this.receiveAnnotations}
+ makeAnnotationDocuments={this.makeAnnotationDocument}
+ getScrollFromPage={this.getScrollFromPage} />);
}
}
@@ -360,14 +254,12 @@ export class Viewer extends React.Component<IViewerProps> {
// file address of the pdf
@action
getPageImage = async (page: number) => {
- 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] =
- <img key={res.path} src={res.path} onError={handleError}
+ <img key={res.path} src={res.path} onError={() => this.getRenderedPage(page)}
style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
} catch (e) {
console.log(e);
@@ -375,33 +267,14 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- @computed get scrollY(): number { return this.props.scrollY; }
-
- // startIndex: where to start rendering pages
- @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.scrollY) - 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].height) + 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);
- }
- }
- }
+ numberRange(this.props.pdf.numPages).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
- private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
+ renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
if (removeOldAnnotations) {
this._annotations = annotations;
}
@@ -412,6 +285,21 @@ export class Viewer extends React.Component<IViewerProps> {
}
@action
+ prevAnnotation = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.Index = Math.max(this.Index - 1, 0);
+ }
+
+ @action
+ nextAnnotation = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.Index = Math.min(this.Index + 1, this.filteredAnnotations.length - 1);
+ }
+
+ sendAnnotations = (page: number) => {
+ return this._savedAnnotations.getValue(page);
+ }
+
receiveAnnotations = (annotations: HTMLDivElement[], page: number) => {
if (page === -1) {
this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
@@ -422,28 +310,21 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- sendAnnotations = (page: number): HTMLDivElement[] | undefined => {
- return this._savedAnnotations.getValue(page);
- }
-
// get the page index that the vertical offset passed in is on
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;
}
getScrollFromPage = (index: number): number => {
- let counter = 0;
- for (let i = 0; i < Math.min(this.props.pdf.numPages, index); i++) {
- counter += this._pageSizes[i].height;
- }
- return counter;
+ return numberRange(Math.min(this.props.pdf.numPages, index)).reduce((counter, i) => counter + this._pageSizes[i].height, 0);
}
+ @action
createAnnotation = (div: HTMLDivElement, page: number) => {
if (this._annotationLayer.current) {
if (div.style.top) {
@@ -461,101 +342,30 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- renderAnnotation = (anno: Doc, index: number): JSX.Element => {
- return <Annotation anno={anno} index={index} parent={this} key={`${anno[Id]}-annotation`} />;
- }
-
- @action
- pointerDown = () => {
- // this._searching = false;
- }
-
@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
- });
- });
- }
+ else if (this._mainCont.current) {
+ let executeFind = () => this._pdfFindController.executeCommand('find', {
+ caseSensitive: false,
+ findPrevious: undefined,
+ highlightAll: true,
+ phraseSearch: true,
+ query: searchString
+ });
+ 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
- // });
- // }
}
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._searchString = e.currentTarget.value;
- }
@action
toggleSearch = (e: React.MouseEvent) => {
@@ -563,29 +373,19 @@ export class Viewer extends React.Component<IViewerProps> {
this._searching = !this._searching;
if (this._searching) {
- let container = this._mainCont.current;
- let viewer = this._viewer.current;
-
- if (!this._pdfFindController) {
- if (container && viewer) {
- let simpleLinkService = new SimpleLinkService();
- this._pdfViewer = new PDFJSViewer.PDFViewer({
- container: container,
- viewer: viewer,
- linkService: simpleLinkService
- });
- simpleLinkService.setPdf(this.props.pdf);
- 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;
- }
+ if (!this._pdfFindController && this._mainCont.current && this._viewer.current) {
+ let simpleLinkService = new SimpleLinkService();
+ this._pdfViewer = new PDFJSViewer.PDFViewer({
+ container: this._mainCont.current,
+ viewer: this._viewer.current,
+ linkService: simpleLinkService
+ });
+ simpleLinkService.setPdf(this.props.pdf);
+ this._mainCont.current.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1);
+ this._mainCont.current.addEventListener("pagerendered", () => console.log("rendered"));
+ this._pdfViewer.setDocument(this.props.pdf);
+ this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
+ this._pdfViewer.findController = this._pdfFindController;
}
}
else {
@@ -599,120 +399,45 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- @action
- prevAnnotation = (e: React.MouseEvent) => {
- e.stopPropagation();
-
- // if (this.Index > 0) {
- // this.Index--;
- // }
- this.Index = Math.max(this.Index - 1, 0);
- }
-
- @action
- nextAnnotation = (e: React.MouseEvent) => {
- e.stopPropagation();
-
- let compiled = this._script;
- let filtered = this._annotations.filter(anno => {
- if (compiled && compiled.compiled) {
- let run = compiled.run({ this: anno });
- if (run.success) {
- return run.result;
- }
- }
- return true;
- });
- 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 (
- <div ref={this._mainCont} style={{ pointerEvents: "all" }} onPointerDown={this.pointerDown}>
- <div className="viewer" style={this._searching ? { position: "absolute", top: 0 } : {}}>
- {this._visibleElements}
- </div>
- <div className="pdfViewer-text" ref={this._viewer} onCopy={() => console.log("gello world")} style={{ transform: "scale(1.5)", transformOrigin: "top left" }} />
- <div className="pdfViewer-annotationLayer"
- style={{
- height: this.props.parent.Document.nativeHeight, width: `100%`,
- pointerEvents: this.props.parent.props.active() ? "none" : "all"
- }}>
- <div className="pdfViewer-annotationLayer-subCont" ref={this._annotationLayer}>
- {this._annotations.filter(anno => {
- if (compiled && compiled.compiled) {
- let run = compiled.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))}
- </div>
- </div>
- <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
- style={{
- bottom: -this.props.scrollY,
- left: `${this._searching ? 0 : 100}%`
- }}>
- <button className="pdfViewer-overlayButton" title="Open Search Bar"></button>
- {/* <button title="Previous Result" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="arrow-up" size="3x" color="white" /></button>
- <button title="Next Result" onClick={this.nextResult}><FontAwesomeIcon icon="arrow-down" size="3x" color="white" /></button> */}
- <input onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} />
- <button title="Search" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="search" size="3x" color="white" /></button>
- </div>
- <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
- style={{ bottom: -this.props.scrollY + 280, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" />
- </div>
- </button>
- <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation"
- style={{ bottom: -this.props.scrollY + 200, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" />
- </div>
- </button>
- <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar"
- style={{ bottom: -this.props.scrollY + 10, right: 0, display: this.props.parent.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" />
- </div>
- </button>
- </div >
- );
+ return (<div className="pdfViewer-viewer" ref={this._mainCont} >
+ <div className="pdfViewer-visibleElements" style={this._searching ? { position: "absolute", top: 0 } : {}}>
+ {this._visibleElements}
+ </div>
+ <div className="pdfViewer-text" ref={this._viewer} />
+ <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
+ {this.filteredAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
+ <Annotation {...this.props} ParentIndex={this.getIndex} anno={anno} index={index} key={`${anno[Id]}-annotation`} />)}
+ </div>
+ <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
+ style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}>
+ <button className="pdfViewer-overlayButton" title="Open Search Bar" />
+ <input className="pdfViewer-overlaySearchBar" placeholder="Search" onChange={this.searchStringChanged}
+ onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} />
+ <button title="Search" onClick={() => this.search(this._searchString)}>
+ <FontAwesomeIcon icon="search" size="3x" color="white" /></button>
+ </div>
+ <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
+ style={{ bottom: -this.props.panY + 280, right: 10, display: this.props.active() ? "flex" : "none" }}>
+ <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" /></div>
+ </button>
+ <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation"
+ style={{ bottom: -this.props.panY + 200, right: 10, display: this.props.active() ? "flex" : "none" }}>
+ <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" /></div>
+ </button>
+ <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar"
+ style={{ bottom: -this.props.panY + 10, right: 0, display: this.props.active() ? "flex" : "none" }}>
+ <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
+ <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" /></div>
+ </button>
+ </div >);
}
}
-export enum AnnotationTypes {
- Region
-}
+export enum AnnotationTypes { Region }
class SimpleLinkService {
externalLinkTarget: any = null;
@@ -731,20 +456,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
diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss
new file mode 100644
index 000000000..af1628a6f
--- /dev/null
+++ b/src/client/views/pdf/Page.scss
@@ -0,0 +1,31 @@
+
+.pdfPage-cont {
+ position: relative;
+
+ .pdfPage-canvasContainer {
+ position: absolute;
+ }
+
+ .pdfPage-dragAnnotationBox {
+ position: absolute;
+ background-color: transparent;
+ opacity: 0.1;
+ }
+
+ .pdfPage-textLayer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ div {
+ user-select: text;
+ }
+ span {
+ color: transparent;
+ position: absolute;
+ white-space: pre;
+ cursor: text;
+ -webkit-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index c5b2a1dda..7ca9d2d7d 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -3,38 +3,35 @@ import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
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 { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
-import { PDFBox } from "../nodes/PDFBox";
import PDFMenu from "./PDFMenu";
import { scale } from "./PDFViewer";
-import "./PDFViewer.scss";
+import "./Page.scss";
import React = require("react");
interface IPageProps {
size: { width: number, height: number };
- pdf: Opt<Pdfjs.PDFDocumentProxy>;
+ pdf: Pdfjs.PDFDocumentProxy;
name: string;
numPages: number;
page: number;
- pageLoaded: (index: number, page: Pdfjs.PDFPageViewport) => void;
- parent: PDFBox;
+ pageLoaded: (page: Pdfjs.PDFPageViewport) => void;
+ fieldExtensionDoc: Doc,
+ Document: Doc,
renderAnnotations: (annotations: Doc[], removeOld: boolean) => void;
- makePin: (x: number, y: number, page: number) => 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;
}
@observer
export default class Page extends React.Component<IPageProps> {
- @observable private _state: string = "N/A";
+ @observable private _state: "N/A" | "rendering" = "N/A";
@observable private _width: number = this.props.size.width;
@observable private _height: number = this.props.size.height;
@observable private _page: Opt<Pdfjs.PDFPageProxy>;
@@ -43,90 +40,44 @@ export default class Page extends React.Component<IPageProps> {
@observable private _marqueeY: number = 0;
@observable private _marqueeWidth: number = 0;
@observable private _marqueeHeight: number = 0;
- @observable private _rotate: string = "";
- private _canvas: React.RefObject<HTMLCanvasElement>;
- private _textLayer: React.RefObject<HTMLDivElement>;
- private _annotationLayer: React.RefObject<HTMLDivElement>;
- private _marquee: React.RefObject<HTMLDivElement>;
- // private _curly: React.RefObject<HTMLImageElement>;
+ private _canvas: React.RefObject<HTMLCanvasElement> = React.createRef();
+ private _textLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _marquee: React.RefObject<HTMLDivElement> = React.createRef();
private _marqueeing: boolean = false;
private _reactionDisposer?: IReactionDisposer;
private _startY: number = 0;
private _startX: number = 0;
- constructor(props: IPageProps) {
- super(props);
- this._canvas = React.createRef();
- this._textLayer = React.createRef();
- this._annotationLayer = React.createRef();
- this._marquee = React.createRef();
- // this._curly = React.createRef();
- }
+ componentDidMount = (): void => this.loadPage(this.props.pdf);
- componentDidMount = (): void => {
- if (this.props.pdf) {
- this.update(this.props.pdf);
- }
- }
+ componentDidUpdate = (): void => this.loadPage(this.props.pdf);
- componentWillUnmount = (): void => {
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
- }
+ componentWillUnmount = (): void => this._reactionDisposer && this._reactionDisposer();
- componentDidUpdate = (): void => {
- if (this.props.pdf) {
- this.update(this.props.pdf);
- }
- }
-
- private update = (pdf: Pdfjs.PDFDocumentProxy): void => {
- if (pdf) {
- this.loadPage(pdf);
- }
- else {
- this._state = "loading";
- }
- }
-
- private loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => {
- if (this._state === "rendering" || this._page) return;
-
- 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
- let viewport = page.getViewport(scale);
- let canvas = this._canvas.current;
- let textLayer = this._textLayer.current;
- if (canvas && textLayer) {
- let ctx = canvas.getContext("2d");
- canvas.width = viewport.width;
- this._width = viewport.width;
- canvas.height = viewport.height;
- this._height = viewport.height;
- this.props.pageLoaded(this._currPage, viewport);
+ 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;
+ this.props.pageLoaded(viewport);
+ let ctx = this._canvas.current.getContext("2d");
if (ctx) {
- // renders the page onto the canvas context
- page.render({ canvasContext: ctx, viewport: viewport });
- // renders text onto the text container
- page.getTextContent().then((res: Pdfjs.TextContent): void => {
+ page.render({ canvasContext: ctx, viewport: viewport }); // renders the page onto the canvas context
+ page.getTextContent().then(res => // renders text onto the text container
//@ts-ignore
Pdfjs.renderTextLayer({
textContent: res,
- container: textLayer,
+ container: this._textLayer.current,
viewport: viewport
- });
- });
+ }));
this._page = page;
}
@@ -134,15 +85,10 @@ export default class Page extends React.Component<IPageProps> {
}
@action
- highlight = (targetDoc?: Doc, color: string = "red") => {
+ highlight = (targetDoc: Doc | undefined, color: string) => {
// creates annotation documents for current highlights
- let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, scale, color, false);
- let targetAnnotations = Cast(this.props.parent.fieldExtensionDoc.annotations, listSpec(Doc));
- if (targetAnnotations === undefined) {
- Doc.GetProto(this.props.parent.fieldExtensionDoc).annotations = new List([annotationDoc]);
- } else {
- targetAnnotations.push(annotationDoc);
- }
+ let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, color, false);
+ Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc);
return annotationDoc;
}
@@ -154,29 +100,19 @@ export default class Page extends React.Component<IPageProps> {
startDrag = (e: PointerEvent, ele: HTMLElement): void => {
e.preventDefault();
e.stopPropagation();
- let thisDoc = this.props.parent.Document;
- // document that this annotation is linked to
- let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" });
- targetDoc.targetPage = this.props.page;
- let annotationDoc = this.highlight(undefined, "red");
- annotationDoc.linkedToDoc = false;
- // create dragData and star tdrag
- let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc);
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, "red");
+ annotationDoc.linkedToDoc = false;
+ let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
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;
- });
- }
- let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
- if (pdfDoc) {
- DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
- }
+ annotations && annotations.forEach(anno => anno.target = targetDoc);
+ DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`)
}
}
},
@@ -187,57 +123,44 @@ export default class Page extends React.Component<IPageProps> {
// cleans up events and boolean
endDrag = (e: PointerEvent): void => {
- // document.removeEventListener("pointermove", this.startDrag);
- // document.removeEventListener("pointerup", this.endDrag);
e.stopPropagation();
}
createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => {
- let doc = this.props.parent.Document;
- let view = Doc.MakeAlias(doc);
- let data = Doc.MakeDelegate(doc.proto!);
+ let view = Doc.MakeAlias(this.props.Document);
+ let data = Doc.MakeDelegate(Doc.GetProto(this.props.Document));
data.title = StrCast(data.title) + "_snippet";
view.proto = data;
view.nativeHeight = marquee.height;
- view.height = (doc[WidthSym]() / NumCast(doc.nativeWidth)) * marquee.height;
- view.nativeWidth = doc.nativeWidth;
+ view.height = (this.props.Document[WidthSym]() / NumCast(this.props.Document.nativeWidth)) * marquee.height;
+ view.nativeWidth = this.props.Document.nativeWidth;
view.startY = marquee.top + this.props.getScrollFromPage(this.props.page);
- view.width = doc[WidthSym]();
- let dragData = new DragManager.DocumentDragData([view], [undefined]);
- DragManager.StartDocumentDrag([], dragData, 0, 0);
+ view.width = this.props.Document[WidthSym]();
+ DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view], [undefined]), 0, 0);
}
@action
onPointerDown = (e: React.PointerEvent): void => {
// if alt+left click, drag and annotate
- if (e.altKey && e.button === 0) {
- e.stopPropagation();
-
- // document.removeEventListener("pointermove", this.startDrag);
- // document.addEventListener("pointermove", this.startDrag);
- // document.removeEventListener("pointerup", this.endDrag);
- // document.addEventListener("pointerup", this.endDrag);
- }
- else if (e.button === 0) {
+ if (NumCast(this.props.Document.scale, 1) !== 1) return;
+ if (!e.altKey && e.button === 0) {
PDFMenu.Instance.StartDrag = this.startDrag;
PDFMenu.Instance.Highlight = this.highlight;
PDFMenu.Instance.Snippet = this.createSnippet;
PDFMenu.Instance.Status = "pdf";
PDFMenu.Instance.fadeOut(true);
- let target: any = e.target;
- if (target && target.parentElement === this._textLayer.current) {
+ if (e.target && (e.target as any).parentElement === this._textLayer.current) {
e.stopPropagation();
}
else {
// set marquee x and y positions to the spatially transformed position
- let current = this._textLayer.current;
- if (current) {
- let boundingRect = current.getBoundingClientRect();
- this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
- this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
+ if (this._textLayer.current) {
+ let boundingRect = this._textLayer.current.getBoundingClientRect();
+ this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width);
+ this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height);
}
this._marqueeing = true;
- if (this._marquee.current) this._marquee.current.style.opacity = "0.2";
+ this._marquee.current && (this._marquee.current.style.opacity = "0.2");
}
document.removeEventListener("pointermove", this.onSelectStart);
document.addEventListener("pointermove", this.onSelectStart);
@@ -251,97 +174,41 @@ export default class Page extends React.Component<IPageProps> {
@action
onSelectStart = (e: PointerEvent): void => {
- let target: any = e.target;
- if (this._marqueeing) {
- let current = this._textLayer.current;
- if (current) {
- // transform positions and find the width and height to set the marquee to
- let boundingRect = current.getBoundingClientRect();
- this._marqueeWidth = ((e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width)) - this._startX;
- this._marqueeHeight = ((e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height)) - this._startY;
- this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
- this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
- this._marqueeWidth = Math.abs(this._marqueeWidth);
- this._marqueeHeight = Math.abs(this._marqueeHeight);
- let { background, opacity, transform: transform } = this.getCurlyTransform();
- if (this._marquee.current /*&& this._curly.current*/) {
- this._marquee.current.style.background = background;
- // this._curly.current.style.opacity = opacity;
- this._rotate = transform;
- }
- }
+ if (this._marqueeing && this._textLayer.current) {
+ // transform positions and find the width and height to set the marquee to
+ let boundingRect = this._textLayer.current.getBoundingClientRect();
+ this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)) - this._startX;
+ this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)) - this._startY;
+ this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
+ this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
+ this._marqueeWidth = Math.abs(this._marqueeWidth);
e.stopPropagation();
e.preventDefault();
}
- else if (target && target.parentElement === this._textLayer.current) {
+ else if (e.target && (e.target as any).parentElement === this._textLayer.current) {
e.stopPropagation();
}
}
- getCurlyTransform = (): { background: string, opacity: string, transform: string } => {
- // let background = "", opacity = "", transform = "";
- // if (this._marquee.current && this._curly.current) {
- // if (this._marqueeWidth > 100 && this._marqueeHeight > 100) {
- // background = "red";
- // opacity = "0";
- // }
- // else {
- // background = "transparent";
- // opacity = "1";
- // }
-
- // // split up for simplicity, could be done in a nested ternary. please do not. -syip2
- // let ratio = this._marqueeWidth / this._marqueeHeight;
- // if (ratio > 1.5) {
- // // vertical
- // transform = "rotate(90deg) scale(1, 5)";
- // }
- // else if (ratio < 0.5) {
- // // horizontal
- // transform = "scale(2, 1)";
- // }
- // else {
- // // diagonal
- // transform = "rotate(45deg) scale(1.5, 1.5)";
- // }
- // }
- return { background: "red", opacity: "0.5", transform: "" };
- }
-
@action
onSelectEnd = (e: PointerEvent): void => {
if (this._marqueeing) {
this._marqueeing = false;
- if (this._marquee.current) {
- 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
- let { background, opacity, transform } = this.getCurlyTransform();
- copy.style.background = background;
- // if curly bracing, add a curly brace
- // if (opacity === "1" && this._curly.current) {
- // copy.style.opacity = opacity;
- // let img = this._curly.current.cloneNode();
- // (img as any).style.opacity = opacity;
- // (img as any).style.transform = transform;
- // copy.appendChild(img);
- // }
- // else {
- copy.style.border = style.border;
- copy.style.opacity = style.opacity;
- // }
- copy.className = this._marquee.current.className;
- 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 };
@@ -360,7 +227,6 @@ export default class Page extends React.Component<IPageProps> {
}
}
-
if (PDFMenu.Instance.Highlighting) {
this.highlight(undefined, "goldenrod");
}
@@ -374,14 +240,14 @@ export default class Page extends React.Component<IPageProps> {
@action
createTextAnnotation = (sel: Selection, selRange: Range) => {
- let clientRects = selRange.getClientRects();
if (this._textLayer.current) {
let boundingRect = this._textLayer.current.getBoundingClientRect();
+ let clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
let rect = clientRects.item(i);
if (rect && rect.width !== this._textLayer.current.getBoundingClientRect().width && rect.height !== this._textLayer.current.getBoundingClientRect().height) {
let annoBox = document.createElement("div");
- annoBox.className = "pdfViewer-annotationBox";
+ annoBox.className = "pdfPage-annotationBox";
// transforms the positions from screen onto the pdf div
annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString();
annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString();
@@ -392,9 +258,8 @@ export default class Page extends React.Component<IPageProps> {
}
}
let text = selRange.extractContents().textContent;
- if (text) {
- this.props.setSelectionText(text);
- }
+ text && this.props.setSelectionText(text);
+
// clear selection
if (sel.empty) { // Chrome
sel.empty();
@@ -404,35 +269,23 @@ export default class Page extends React.Component<IPageProps> {
}
doubleClick = (e: React.MouseEvent) => {
- let target: any = e.target;
- // if double clicking text
- if (target && target.parentElement === this._textLayer.current) {
+ if (e.target && (e.target as any).parentElement === this._textLayer.current) {
// do something to select the paragraph ideally
}
-
- let current = this._textLayer.current;
- if (current) {
- let boundingRect = current.getBoundingClientRect();
- let x = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
- let y = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
- this.props.makePin(x, y, this.props.page);
- }
}
render() {
return (
- <div onPointerDown={this.onPointerDown} onDoubleClick={this.doubleClick} className={"page-cont"} style={{ "width": this._width, "height": this._height }}>
- <div className="canvasContainer">
- <canvas ref={this._canvas} />
+ <div className={"pdfPage-cont"} onPointerDown={this.onPointerDown} onDoubleClick={this.doubleClick} style={{ "width": this._width, "height": this._height }}>
+ <canvas className="PdfPage-canvasContainer" ref={this._canvas} />
+ <div className="pdfPage-dragAnnotationBox" ref={this._marquee}
+ style={{
+ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`,
+ width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`,
+ border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}`
+ }}>
</div>
- <div className="pdfInkingLayer-cont" ref={this._annotationLayer} style={{ width: "100%", height: "100%", position: "relative", top: "-100%" }}>
- <div className="pdfViewer-annotationBox" ref={this._marquee}
- style={{ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, background: "red", border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}` }}>
- {/* <img ref={this._curly} src="https://static.thenounproject.com/png/331760-200.png" style={{ width: "100%", height: "100%", transform: `${this._rotate}` }} /> */}
- </div>
- </div>
- <div className="textlayer" ref={this._textLayer} style={{ "position": "relative", "top": `-${2 * this._height}px`, "height": `${this._height}px` }} />
- </div>
- );
+ <div className="pdfPage-textlayer" ref={this._textLayer} />
+ </div>);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 11f3eb846..d98b66324 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";
@@ -706,7 +706,7 @@ export default class PresentationElement extends React.Component<PresentationEle
* It makes it possible to show dropping lines on drop targets.
*/
onDragMove = (e: PointerEvent): void => {
- this.props.document.libraryBrush = false;
+ Doc.UnBrushDoc(this.props.document);
let x = this.ScreenToLocalListTransform(e.clientX, e.clientY);
let rect = this.header!.getBoundingClientRect();
let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2);
@@ -843,6 +843,7 @@ export default class PresentationElement extends React.Component<PresentationEle
PanelWidth={() => 350}
PanelHeight={() => 90}
focus={emptyFunction}
+ backgroundColor={returnEmptyString}
selectOnLoad={false}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
@@ -888,7 +889,7 @@ export default class PresentationElement extends React.Component<PresentationEle
style={{
outlineColor: "maroon",
outlineStyle: "dashed",
- outlineWidth: BoolCast(p.document.libraryBrush) ? `1px` : "0px",
+ outlineWidth: Doc.IsBrushed(p.document) ? `1px` : "0px",
}}
onClick={e => { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}>
<strong className="presentationView-name">
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 {
<div className="active-icon description">Collection Filters Active</div>
</div> : undefined}
</div>
- )
+ );
}
// Useful queries:
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0390359b3..8201aa374 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -1,34 +1,30 @@
import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faChartBar, faFilePdf, faFilm, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote, faFingerprint } from '@fortawesome/free-solid-svg-icons';
+import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote } 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 { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
+import { ObjectField } from "../../../new_fields/ObjectField";
+import { RichTextField } from "../../../new_fields/RichTextField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
import { DocumentType } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager, SetupDrag } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
import { SearchUtil } from "../../util/SearchUtil";
import { Transform } from "../../util/Transform";
import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss";
import { CollectionViewType } from "../collections/CollectionBaseView";
import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { ContextMenu } from "../ContextMenu";
import { DocumentView } from "../nodes/DocumentView";
import { SearchBox } from "./SearchBox";
import "./SearchItem.scss";
import "./SelectorContextMenu.scss";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { ContextMenu } from "../ContextMenu";
-import { faFile } from '@fortawesome/free-solid-svg-icons';
-import { DocServer } from "../../DocServer";
export interface SearchItemProps {
doc: Doc;
@@ -109,23 +105,11 @@ export interface LinkMenuProps {
@observer
export class LinkContextMenu extends React.Component<LinkMenuProps> {
- highlightDoc = (doc: Doc) => {
- return () => {
- doc.libraryBrush = true;
- };
- }
+ highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc);
- unHighlightDoc = (doc: Doc) => {
- return () => {
- doc.libraryBrush = false;
- };
- }
+ unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc);
- getOnClick(col: Doc) {
- return () => {
- CollectionDockingView.Instance.AddRightSplit(col, undefined);
- };
- }
+ getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined);
render() {
return (
@@ -134,7 +118,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {
<div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div>
<div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div>
</div>
- )
+ );
}
}
@@ -223,6 +207,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
PanelWidth={returnXDimension}
PanelHeight={returnYDimension}
focus={emptyFunction}
+ backgroundColor={returnEmptyString}
selectOnLoad={false}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
@@ -289,14 +274,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
let doc2 = Cast(this.props.doc.anchor2, Doc, null);
- doc1 && (doc1.libraryBrush = true);
- doc2 && (doc2.libraryBrush = true);
+ Doc.BrushDoc(doc1);
+ Doc.BrushDoc(doc2);
}
} else {
- let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
- docViews.forEach(element => {
- element.props.Document.libraryBrush = true;
- });
+ DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element =>
+ Doc.BrushDoc(element.props.Document));
}
}
@@ -306,14 +289,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
let doc2 = Cast(this.props.doc.anchor2, Doc, null);
- doc1 && (doc1.libraryBrush = false);
- doc2 && (doc2.libraryBrush = false);
+ Doc.UnBrushDoc(doc1);
+ Doc.UnBrushDoc(doc2);
}
} else {
- let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
- docViews.forEach(element => {
- element.props.Document.libraryBrush = false;
- });
+ DocumentManager.Instance.getAllDocumentViews(this.props.doc).
+ forEach(element => Doc.UnBrushDoc(element.props.Document));
}
}
@@ -354,7 +335,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
</div>
<div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}>
<div className={`icon-${this._useIcons ? "icons" : "live"}`}>
- <div className="search-type" title="Click to Preview">{this.DocumentIcon}</div>
+ <div className="search-type" title="Click to Preview">{this.DocumentIcon()}</div>
<div className="search-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div>
</div>
<div className="link-container item">
@@ -365,8 +346,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
</div>
</div>
<div className="searchBox-instances">
- {(doc1 instanceof Doc && doc2 instanceof Doc) ? this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> :
- <SelectorContextMenu {...this.props} /> : null}
+ {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> :
+ <SelectorContextMenu {...this.props} />}
</div>
</div>
);
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<ToggleBarProps>{
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/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<typeof schema2>;
-
-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<Doc>();
- //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 <div><button onClick={this.onClick}>Click me</button>
- {/* <input onKeyPress={this.onEnter}></input> */}
- </div>;
+ return (
+ <div>
+ <button onClick={this.onCreateClick}>Create Docs</button>
+ <button onClick={this.onReadClick}>Read Docs</button>
+ <button onClick={this.onDeleteClick}>Delete Docs</button>
+ </div>
+ );
}
}
+DocServer.init(window.location.protocol, window.location.hostname, 4321, "test");
ReactDOM.render(
<Test />,
document.getElementById('root')
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 84b8589dd..543ee46cc 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -8,11 +8,12 @@ import { listSpec } from "./Schema";
import { ObjectField } from "./ObjectField";
import { RefField, FieldId } from "./RefField";
import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
-import { scriptingGlobal } from "../client/util/Scripting";
+import { scriptingGlobal, CompileScript, Scripting } from "../client/util/Scripting";
import { List } from "./List";
import { DocumentType } from "../client/documents/Documents";
-import { ComputedField } from "./ScriptField";
-import { PrefetchProxy } from "./Proxy";
+import { ComputedField, ScriptField } from "./ScriptField";
+import { PrefetchProxy, ProxyField } from "./Proxy";
+import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -68,6 +69,8 @@ export function DocListCast(field: FieldResult): Doc[] {
export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
+export const UpdatingFromServer = Symbol("UpdatingFromServer");
+const CachedUpdates = Symbol("Cached updates");
function fetchProto(doc: Doc) {
const proto = doc.proto;
@@ -76,8 +79,6 @@ function fetchProto(doc: Doc) {
}
}
-let updatingFromServer = false;
-
@scriptingGlobal
@Deserializable("Doc", fetchProto).withFields(["id"])
export class Doc extends RefField {
@@ -131,8 +132,10 @@ export class Doc extends RefField {
//{ [key: string]: Field | FieldWaiting | undefined }
private ___fields: any = {};
+ private [UpdatingFromServer]: boolean = false;
+
private [Update] = (diff: any) => {
- if (updatingFromServer) {
+ if (this[UpdatingFromServer]) {
return;
}
DocServer.UpdateField(this[Id], diff);
@@ -147,18 +150,29 @@ export class Doc extends RefField {
return "invalid";
}
+ private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
+
public async [HandleUpdate](diff: any) {
const set = diff.$set;
+ const sameAuthor = this.author === CurrentUserUtils.email;
if (set) {
for (const key in set) {
if (!key.startsWith("fields.")) {
continue;
}
- const value = await SerializationHelper.Deserialize(set[key]);
const fKey = key.substring(7);
- updatingFromServer = true;
- this[fKey] = value;
- updatingFromServer = false;
+ const fn = async () => {
+ const value = await SerializationHelper.Deserialize(set[key]);
+ this[UpdatingFromServer] = true;
+ this[fKey] = value;
+ this[UpdatingFromServer] = false;
+ };
+ if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ delete this[CachedUpdates][fKey];
+ await fn();
+ } else {
+ this[CachedUpdates][fKey] = fn;
+ }
}
}
const unset = diff.$unset;
@@ -168,9 +182,17 @@ export class Doc extends RefField {
continue;
}
const fKey = key.substring(7);
- updatingFromServer = true;
- delete this[fKey];
- updatingFromServer = false;
+ const fn = () => {
+ this[UpdatingFromServer] = true;
+ delete this[fKey];
+ this[UpdatingFromServer] = false;
+ };
+ if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ delete this[CachedUpdates][fKey];
+ await fn();
+ } else {
+ this[CachedUpdates][fKey] = fn;
+ }
}
}
}
@@ -187,6 +209,21 @@ export namespace Doc {
// return Cast(field, ctor);
// });
// }
+ export function RunCachedUpdate(doc: Doc, field: string) {
+ const update = doc[CachedUpdates][field];
+ if (update) {
+ update();
+ delete doc[CachedUpdates][field];
+ }
+ }
+ export function AddCachedUpdate(doc: Doc, field: string, oldValue: any) {
+ const val = oldValue;
+ doc[CachedUpdates][field] = () => {
+ doc[UpdatingFromServer] = true;
+ doc[field] = val;
+ doc[UpdatingFromServer] = false;
+ };
+ }
export function MakeReadOnly(): { end(): void } {
makeReadOnly();
return {
@@ -341,19 +378,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, 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);
@@ -361,10 +403,15 @@ export namespace Doc {
}
}
export function MakeAlias(doc: Doc) {
- if (!GetT(doc, "isPrototype", "boolean", true)) {
- return Doc.MakeCopy(doc);
+ let alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc) : Doc.MakeDelegate(doc);
+ let aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1;
+ let script = `return renameAlias(self, ${aliasNumber})`;
+ //let script = "StrCast(self.title).replace(/\\([0-9]*\\)/, \"\") + `(${n})`";
+ let compiled = CompileScript(script, { params: { this: "Doc" }, capturedVariables: { self: doc }, typecheck: false });
+ if (compiled.compiled) {
+ alias.title = new ComputedField(compiled);
}
- return Doc.MakeDelegate(doc); // bcz?
+ return alias;
}
//
@@ -420,7 +467,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 +478,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,19 +572,37 @@ export namespace Doc {
}
export function UseDetailLayout(d: Doc) {
runInAction(async () => {
- let detailLayout1 = await PromiseValue(d.detailedLayout);
- let detailLayout = await PromiseValue(d.detailedLayout);
+ 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;
+ let delegDetailLayout = Doc.MakeDelegate(detailLayout);
d.layout = delegDetailLayout;
- let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout);
- let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout);
- delegDetailLayout.layout = subDetailLayout;
+ delegDetailLayout.layout = await delegDetailLayout.detailedLayout;
}
}
});
}
-} \ No newline at end of file
+
+ export class DocBrush {
+ @observable BrushedDoc: Doc[] = [];
+ }
+ const manager = new DocBrush();
+ export function IsBrushed(doc: Doc) {
+ return manager.BrushedDoc.some(d => Doc.AreProtosEqual(d, doc));
+ }
+ export function IsBrushedDegree(doc: Doc) {
+ return manager.BrushedDoc.some(d => d === doc) ? 2 : Doc.IsBrushed(doc) ? 1 : 0;
+ }
+ export function BrushDoc(doc: Doc) {
+ if (manager.BrushedDoc.indexOf(doc) === -1) runInAction(() => manager.BrushedDoc.push(doc));
+ }
+ export function UnBrushDoc(doc: Doc) {
+ let index = manager.BrushedDoc.indexOf(doc);
+ if (index !== -1) runInAction(() => manager.BrushedDoc.splice(index, 1));
+ }
+}
+Scripting.addGlobal(function renameAlias(doc: any, n: any) {
+ return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`;
+}); \ No newline at end of file
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<T extends RefField> extends ObjectField {
@@ -68,6 +69,34 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
}
+export namespace ProxyField {
+ let useProxy = true;
+ export function DisableProxyFields() {
+ useProxy = false;
+ }
+
+ export function EnableProxyFields() {
+ useProxy = true;
+ }
+
+ export function WithoutProxy<T>(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<RefField>) {
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/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 Field> = T extends RefField ? never : T;
-export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
-export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
-export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
+export type CastCtor = ToConstructor<Field> | ListSpec<Field>;
+
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
if (field instanceof Promise) {
return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal;
}
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 2ebfb9e71..c546e2aac 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -1,12 +1,13 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult } from "./Doc";
+import { Doc, Field, FieldResult, UpdatingFromServer } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
-import { ComputedField } from "./ScriptField";
+import { DocServer } from "../client/DocServer";
+import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -14,7 +15,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[] = [];
@@ -58,18 +59,29 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
delete curValue[Parent];
delete curValue[OnUpdate];
}
- if (value === undefined) {
- delete target.__fields[prop];
- } else {
- target.__fields[prop] = value;
+ const writeMode = DocServer.getFieldWriteMode(prop as string);
+ const fromServer = target[UpdatingFromServer];
+ const sameAuthor = fromServer || (receiver.author === CurrentUserUtils.email);
+ const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly);
+ const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default);
+ if (writeToDoc) {
+ if (value === undefined) {
+ delete target.__fields[prop];
+ } else {
+ target.__fields[prop] = value;
+ }
+ if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
+ if (writeToServer) {
+ if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
+ else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+ } else {
+ DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
+ }
+ UndoManager.AddEvent({
+ redo: () => receiver[prop] = value,
+ undo: () => receiver[prop] = curValue
+ });
}
- if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
- if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
- else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
- UndoManager.AddEvent({
- redo: () => receiver[prop] = value,
- undo: () => receiver[prop] = curValue
- });
return true;
});
@@ -103,9 +115,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;
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index f0f45d8f9..807216ef1 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -17,6 +17,7 @@ dist = "../../server/public/files"
db = MongoClient("localhost", 27017)["Dash"]
target_collection = db.newDocuments
+target_doc_title = "Workspace 1"
schema_guids = []
common_proto_id = ""
@@ -69,7 +70,7 @@ def text_doc_map(string_list):
return listify(proxify_guids(list(map(guid_map, string_list))))
-def write_schema(parse_results, display_fields, storage_key):
+def write_collection(parse_results, display_fields, storage_key, viewType=2):
view_guids = parse_results["child_guids"]
data_doc = parse_results["schema"]
@@ -90,7 +91,7 @@ def write_schema(parse_results, display_fields, storage_key):
"zoomBasis": 1,
"zIndex": 2,
"libraryBrush": False,
- "viewType": 2
+ "viewType": viewType
},
"__type": "Doc"
}
@@ -130,8 +131,7 @@ def write_text_doc(content):
"x": 10,
"y": 10,
"width": 400,
- "zIndex": 2,
- "libraryBrush": False
+ "zIndex": 2
},
"__type": "Doc"
}
@@ -183,8 +183,7 @@ def write_image(folder, name):
"x": 10,
"y": 10,
"width": min(800, native_width),
- "zIndex": 2,
- "libraryBrush": False
+ "zIndex": 2
},
"__type": "Doc"
}
@@ -237,7 +236,7 @@ def parse_document(file_name: str):
copyfile(dir_path + "/" + image, dir_path +
"/" + image.replace(".", "_o.", 1))
copyfile(dir_path + "/" + image, dir_path +
- "/" + image.replace(".", "_m.", 1))
+ "/" + image.replace(".", "_m.", 1))
print(f"extracted {count} images...")
def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace(
@@ -381,22 +380,22 @@ candidates = 0
for file_name in os.listdir(source):
if file_name.endswith('.docx'):
candidates += 1
- schema_guids.append(write_schema(
+ schema_guids.append(write_collection(
parse_document(file_name), ["title", "data"], "image_data"))
print("writing parent schema...")
-parent_guid = write_schema({
+parent_guid = write_collection({
"schema": {
"_id": guid(),
"fields": {},
"__type": "Doc"
},
"child_guids": schema_guids
-}, ["title", "short_description", "original_price"], "data")
+}, ["title", "short_description", "original_price"], "data", 1)
print("appending parent schema to main workspace...\n")
target_collection.update_one(
- {"fields.title": "WS collection 1"},
+ {"fields.title": target_doc_title},
{"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}}
)
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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_3_button_optical_mouse.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Amazon_Kindle_Keyboard.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Apple_Adj_Keyboard.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Apple_Mac_Portable.docx b/src/scraping/buxton/source/Bill_Notes_Apple_Mac_Portable.docx
new file mode 100644
index 000000000..df1aafe9c
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Apple_Mac_Portable.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_BAT.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx b/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx
index 649d636e3..06094b4d3 100644
--- a/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx
+++ b/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Casio_CZ-101.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_FrogPad.docx b/src/scraping/buxton/source/Bill_Notes_FrogPad.docx
index ba80c1959..d01e1bf5c 100644
--- a/src/scraping/buxton/source/Bill_Notes_FrogPad.docx
+++ b/src/scraping/buxton/source/Bill_Notes_FrogPad.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx
index 8558a4e13..b9a30c8a9 100644
--- a/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx
+++ b/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx
index 09e17f971..0615c4953 100644
--- a/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx
+++ b/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Kindle_3G_lighted_cover.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Microwriter.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_NewO.docx b/src/scraping/buxton/source/Bill_Notes_NewO.docx
index 2f4a04e81..a514926d2 100644
--- a/src/scraping/buxton/source/Bill_Notes_NewO.docx
+++ b/src/scraping/buxton/source/Bill_Notes_NewO.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx b/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx
index 3038de363..c0cf6ba9a 100644
--- a/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx
+++ b/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_PARCtab.doc b/src/scraping/buxton/source/Bill_Notes_PARCtab.doc
new file mode 100644
index 000000000..3cdc2d21b
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_PARCtab.doc
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Twiddler.docx
Binary files 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
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_orbiTouch.doc
Binary files differ
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index ea5388004..09b52eadf 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -13,7 +13,7 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
if (field === undefined || field === null) {
continue;
}
- if (field.__type === "proxy") {
+ if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
ids.push(field.fieldId);
} else if (field.__type === "list") {
addDoc(field.fields, ids, files);
diff --git a/src/server/index.ts b/src/server/index.ts
index 67087fc1f..a8f5aca46 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,6 +149,33 @@ app.get("/pull", (req, res) =>
res.redirect("/");
}));
+app.get("/buxton", (req, res) => {
+ 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<string>((resolve, reject) => {
+ let options: ExecOptions = {};
+ if (fromDirectory) {
+ options.cwd = path.join(__dirname, fromDirectory);
+ }
+ 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<string>((resolve, reject) => {
+ fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
+ });
+};
+
app.get("/version", (req, res) => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => {
if (err) {
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 69c766d56..5218a239a 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -2,6 +2,8 @@ import { Database } from "./database";
import { Search } from "./Search";
import * as path from 'path';
+//npx ts-node src/server/remapUrl.ts
+
const suffixMap: { [type: string]: true } = {
"video": true,
"pdf": true,