aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2020-06-08 22:26:42 -0400
committerBob Zeleznik <zzzman@gmail.com>2020-06-08 22:26:42 -0400
commit23d91c839bc7dd14fd906aa1b3c7b3cd16980dd4 (patch)
treec5f181d92e5edad3c12f4500128bc5420eb6616d /src
parent01cc676b568c9d53757e1e4c8155672ecea95562 (diff)
parentc878c35dc06c6f0c653390d95d18feeaf2dfabd1 (diff)
Merge branch 'master' into RichTextEdition
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts15
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts4
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx7
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts9
-rw-r--r--src/client/documents/Documents.ts456
-rw-r--r--src/client/util/CurrentUserUtils.ts61
-rw-r--r--src/client/util/DocumentManager.ts16
-rw-r--r--src/client/util/DragManager.ts2
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx4
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts2
-rw-r--r--src/client/util/InteractionUtils.tsx126
-rw-r--r--src/client/util/LinkManager.ts10
-rw-r--r--src/client/views/AntimodeMenu.tsx1
-rw-r--r--src/client/views/DocComponent.tsx8
-rw-r--r--src/client/views/DocumentDecorations.scss5
-rw-r--r--src/client/views/DocumentDecorations.tsx4
-rw-r--r--src/client/views/GestureOverlay.tsx179
-rw-r--r--src/client/views/GlobalKeyHandler.ts37
-rw-r--r--src/client/views/InkingControl.scss131
-rw-r--r--src/client/views/InkingControl.tsx93
-rw-r--r--src/client/views/InkingStroke.tsx51
-rw-r--r--src/client/views/Main.tsx6
-rw-r--r--src/client/views/MainView.tsx20
-rw-r--r--src/client/views/Touchable.tsx2
-rw-r--r--src/client/views/animationtimeline/Keyframe.tsx68
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx216
-rw-r--r--src/client/views/animationtimeline/Track.tsx5
-rw-r--r--src/client/views/collections/CollectionMapView.tsx2
-rw-r--r--src/client/views/collections/CollectionPileView.tsx8
-rw-r--r--src/client/views/collections/CollectionStackingView.scss42
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx7
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx27
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx11
-rw-r--r--src/client/views/collections/CollectionView.tsx49
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss60
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx202
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx289
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss36
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx140
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx144
-rw-r--r--src/client/views/linking/LinkEditor.tsx2
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx41
-rw-r--r--src/client/views/nodes/ColorBox.tsx49
-rw-r--r--src/client/views/nodes/ComparisonBox.scss16
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx19
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx42
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx16
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx12
-rw-r--r--src/client/views/nodes/VideoBox.tsx7
-rw-r--r--src/client/views/nodes/WebBox.tsx25
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss11
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx18
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx2
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts18
-rw-r--r--src/client/views/pdf/PDFViewer.scss4
-rw-r--r--src/client/views/pdf/PDFViewer.tsx43
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx10
-rw-r--r--src/client/views/search/SearchBox.tsx4
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx6
-rw-r--r--src/client/views/webcam/WebCamLogic.js8
-rw-r--r--src/debug/Repl.tsx3
-rw-r--r--src/debug/Viewer.tsx3
-rw-r--r--src/fields/Doc.ts440
-rw-r--r--src/fields/InkField.ts10
-rw-r--r--src/fields/ScriptField.ts15
-rw-r--r--src/fields/documentSchemas.ts14
-rw-r--r--src/fields/util.ts12
-rw-r--r--src/mobile/ImageUpload.tsx8
-rw-r--r--src/mobile/MobileInterface.tsx7
-rw-r--r--src/pen-gestures/GestureUtils.ts15
-rw-r--r--src/pen-gestures/ndollar.ts17
-rw-r--r--src/scraping/buxton/final/BuxtonImporter.ts74
-rw-r--r--src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdfbin0 -> 107790 bytes
-rw-r--r--src/server/ApiManagers/DownloadManager.ts2
-rw-r--r--src/server/ApiManagers/PDFManager.ts43
-rw-r--r--src/server/DashUploadUtils.ts10
-rw-r--r--src/server/index.ts7
-rw-r--r--src/server/remapUrl.ts3
-rw-r--r--src/server/server_Initialization.ts27
-rw-r--r--src/server/websocket.ts13
-rw-r--r--src/typings/index.d.ts2
87 files changed, 2103 insertions, 1561 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index bcb215804..e527634fd 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,6 +2,7 @@ import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket, Room } from 'socket.io';
import { Message } from './server/Message';
+import { ColorState } from 'react-color';
export namespace Utils {
export let DRAG_THRESHOLD = 4;
@@ -43,7 +44,7 @@ export namespace Utils {
}
/**
- * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the
+ * A convenience method. Prepends the full path (i.e. http://localhost:<port>) to the
* requested extension
* @param extension the specified sub-path to append to the window origin
*/
@@ -75,6 +76,18 @@ export namespace Utils {
document.body.removeChild(textArea);
}
+ export function decimalToHexString(number: number) {
+ if (number < 0) {
+ number = 0xFFFFFFFF + number + 1;
+ }
+ return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
+ }
+
+ export function colorString(color: ColorState) {
+ return color.hex.startsWith("#") ?
+ color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex;
+ }
+
export function fromRGBAstr(rgba: string) {
const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/);
const r = rm ? Number(rm[1]) : 0;
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index fef71ffeb..a604c7de1 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -8,7 +8,7 @@ import { Cast, StrCast } from "../../../fields/Types";
import { ImageField } from "../../../fields/URLField";
import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/SharedTypes";
import { Utils } from "../../../Utils";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox";
import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
@@ -332,7 +332,7 @@ export namespace GooglePhotos {
const url = data.url.href;
const target = Doc.MakeAlias(source);
const description = parseDescription(target, descriptionKey);
- await Doc.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
+ await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
media.push({ url, description });
}
if (media.length) {
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index ce7f49e64..748d571c0 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -6,7 +6,6 @@ import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from "../../documents/Documents";
import { DocumentDecorations } from "../../views/DocumentDecorations";
-import { InkingControl } from "../../views/InkingControl";
import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
import "../../views/nodes/WebBox.scss";
import "./YoutubeBox.scss";
@@ -156,14 +155,14 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
@action
processVideoDetails = (videoDetails: any[]) => {
this.videoDetails = videoDetails;
- this.props.Document.cachedDetails = Docs.Get.FromJson({ data: videoDetails, title: "detailBackUp" });
+ this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: "detailBackUp" });
}
/**
* The function that stores the search results in the props document.
*/
backUpSearchResults = (videos: any[]) => {
- this.props.Document.cachedSearchResults = Docs.Get.FromJson({ data: videos, title: "videosBackUp" });
+ this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: "videosBackUp" });
}
/**
@@ -350,7 +349,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
- const classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+ const classname = "webBox-cont" + (this.props.isSelected() && !Doc.GetSelectedTool() && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
<>
<div className={classname} >
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 517ccdce9..6b0b3e029 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,7 +1,6 @@
import * as request from "request-promise";
import { Doc, Field } from "../../fields/Doc";
import { Cast } from "../../fields/Types";
-import { Docs } from "../documents/Documents";
import { Utils } from "../../Utils";
import { InkData } from "../../fields/InkField";
import { UndoManager } from "../util/UndoManager";
@@ -45,7 +44,11 @@ export enum Confidence {
export namespace CognitiveServices {
const ExecuteQuery = async <D>(service: Service, manager: APIManager<D>, data: D): Promise<any> => {
- const apiKey = process.env[service.toUpperCase()];
+ let apiKey = process.env[service.toUpperCase()];
+ // A HACK FOR A DEMO VIDEO - syip2
+ if (service === "handwriting") {
+ apiKey = "61088486d76c4b12ba578775a5f55422";
+ }
if (!apiKey) {
console.log(`No API key found for ${service}: ensure youe root directory has .env file with _CLIENT_${service.toUpperCase()}.`);
return undefined;
@@ -191,7 +194,7 @@ export namespace CognitiveServices {
let results = await ExecuteQuery(Service.Handwriting, Manager, inkData);
if (results) {
results.recognitionUnits && (results = results.recognitionUnits);
- target[keys[0]] = Docs.Get.FromJson({ data: results, title: "Ink Analysis" });
+ target[keys[0]] = Doc.Get.FromJson({ data: results, title: "Ink Analysis" });
const recognizedText = results.map((item: any) => item.recognizedText);
const recognizedObjects = results.map((item: any) => item.recognizedObject);
const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6b17b4303..8d3e5a771 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -9,11 +9,11 @@ import { ScriptingBox } from "../views/nodes/ScriptingBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
import { OmitKeys, JSONUtils, Utils } from "../../Utils";
-import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../fields/Doc";
+import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast, HeightSym, WidthSym } from "../../fields/Doc";
import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../fields/URLField";
import { HtmlField } from "../../fields/HtmlField";
import { List } from "../../fields/List";
-import { Cast, NumCast, StrCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../fields/Types";
import { DocServer } from "../DocServer";
import { dropActionType } from "../util/DragManager";
import { DateField } from "../../fields/DateField";
@@ -36,11 +36,9 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { QueryBox } from "../views/nodes/QueryBox";
import { ColorBox } from "../views/nodes/ColorBox";
-import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox";
import { DocHolderBox } from "../views/nodes/DocHolderBox";
-import { InkingStroke } from "../views/InkingStroke";
+import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../views/InkingStroke";
import { InkField } from "../../fields/InkField";
-import { InkingControl } from "../views/InkingControl";
import { RichTextField } from "../../fields/RichTextField";
import { extname } from "path";
import { MessageStore } from "../../server/Message";
@@ -49,6 +47,8 @@ import { ContextMenu } from "../views/ContextMenu";
import { LinkBox } from "../views/nodes/LinkBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ComparisonBox } from "../views/nodes/ComparisonBox";
+import { runInAction } from "mobx";
+import { UndoManager } from "../util/UndoManager";
const path = require('path');
export interface DocumentOptions {
@@ -126,6 +126,9 @@ export interface DocumentOptions {
curPage?: number;
currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+ currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
+ lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
+ activeFrame?: number; // the active frame of a document in a frame base collection
borderRounding?: string;
boxShadow?: string;
dontRegisterChildViews?: boolean;
@@ -142,7 +145,7 @@ export interface DocumentOptions {
dbDoc?: Doc;
linkRelationship?: string; // type of relatinoship a link represents
ischecked?: ScriptField; // returns whether a font icon box is checked
- activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
+ activeInkPen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
onClick?: ScriptField;
onDoubleClick?: ScriptField;
onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
@@ -429,19 +432,30 @@ export namespace Docs {
parentProto.data = new List<Doc>();
}
if (device) {
- const { __images } = device;
+ const { title, __images, additionalMedia } = device;
delete device.__images;
+ delete device.additionalMedia;
const { ImageDocument, StackingDocument } = Docs.Create;
const constructed = __images.map(({ url, nativeWidth, nativeHeight }) => ({ url: Utils.prepend(url), nativeWidth, nativeHeight }));
- const deviceImages = constructed.map(({ url, nativeWidth, nativeHeight }, i) => ImageDocument(url, {
- title: `image${i}.${extname(url)}`,
- _nativeWidth: nativeWidth,
- _nativeHeight: nativeHeight
- }));
+ const deviceImages = constructed.map(({ url, nativeWidth, nativeHeight }, i) => {
+ const imageDoc = ImageDocument(url, {
+ title: `image${i}.${extname(url)}`,
+ _nativeWidth: nativeWidth,
+ _nativeHeight: nativeHeight
+ });
+ const media = additionalMedia[i];
+ if (media) {
+ for (const key of Object.keys(media)) {
+ imageDoc[`additionalMedia_${key}`] = Utils.prepend(`/files/${key}/buxton/${media[key]}`);
+ }
+ }
+ return imageDoc;
+ });
// the main document we create
- const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true, hero: new ImageField(constructed[0].url) });
+ const doc = StackingDocument(deviceImages, { title, _LODdisable: true, hero: new ImageField(constructed[0].url) });
+ doc.nameAliases = new List<string>([title.toLowerCase()]);
// add the parsed attributes to this main document
- Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
+ Doc.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
Doc.AddDocToList(parentProto, "data", doc);
} else if (errors) {
console.log(errors);
@@ -500,6 +514,7 @@ export namespace Docs {
const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
+ viewDoc.author = Doc.CurrentUserEmail;
viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
return Doc.assign(viewDoc, delegateProps, true);
@@ -616,12 +631,13 @@ export namespace Docs {
return doc;
}
- export function InkDocument(color: string, tool: number, strokeWidth: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
+ export function InkDocument(color: string, tool: number, strokeWidth: string, strokeBezier: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
const I = new Doc();
I.type = DocumentType.INK;
I.layout = InkingStroke.LayoutString("data");
I.color = color;
I.strokeWidth = strokeWidth;
+ I.strokeBezier = strokeBezier;
I.tool = tool;
I.title = "ink";
I.x = options.x;
@@ -762,225 +778,6 @@ export namespace Docs {
return InstanceFromProto(proto, undefined, options);
}
}
-
- export namespace Get {
-
- const primitives = ["string", "number", "boolean"];
-
- export interface JsonConversionOpts {
- data: any;
- title?: string;
- appendToExisting?: { targetDoc: Doc, fieldKey?: string };
- excludeEmptyObjects?: boolean;
- }
-
- const defaultKey = "json";
-
- /**
- * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
- * deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
- *
- * After building a hierarchy within / below a top-level document, it then returns that top-level parent.
- *
- * If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the
- * string is invalid JSON, so we should assume that the input is the result of a JSON.parse()
- * call that returned a regular string value to be stored as a Field.
- *
- * If we've received something other than a string, since the caller might also pass in the results of a
- * JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number.
- * Anything else (like a function, etc. passed in naively as any) is meaningless for this operation.
- *
- * All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
- * lacking the key value structure, gets stored as a field in a wrapper document.
- *
- * @param data for convenience and flexibility, either a valid JSON string to be parsed,
- * or the result of any JSON.parse() call.
- * @param title an optional title to give to the highest parent document in the hierarchy.
- * If whether this function creates a new document or appendToExisting is specified and that document already has a title,
- * because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
- * @param appendToExisting **if specified**, there are two cases, both of which return the target document:
- *
- * 1) the json to be converted can be represented as a document, in which case the target document will act as the root
- * of the tree and receive all the conversion results as new fields on itself
- * 2) the json can't be represented as a document, in which case the function will assign the field-level conversion
- * results to either the specified key on the target document, or to its "json" key by default.
- *
- * If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls)
- * to act as the root of the tree.
- *
- * One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
- * say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
- * from a default call to new Doc.
- *
- * @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
- * if they contain no data. By default, empty objects and arrays are ignored.
- */
- export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
- if (excludeEmptyObjects === undefined) {
- excludeEmptyObjects = true;
- }
- if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
- return undefined;
- }
- let resolved: any;
- try {
- resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
- } catch (e) {
- return undefined;
- }
- let output: Opt<Doc>;
- if (typeof resolved === "object" && !(resolved instanceof Array)) {
- output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
- } else {
- const result = toField(resolved, excludeEmptyObjects);
- if (appendToExisting) {
- (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
- } else {
- (output = new Doc).json = result;
- }
- }
- title && output && (output.title = title);
- return output;
- }
-
- /**
- * For each value of the object, recursively convert it to its appropriate field value
- * and store the field at the appropriate key in the document if it is not undefined
- * @param object the object to convert
- * @returns the object mapped from JSON to field values, where each mapping
- * might involve arbitrary recursion (since toField might itself call convertObject)
- */
- const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
- const hasEntries = Object.keys(object).length;
- if (hasEntries || !excludeEmptyObjects) {
- const resolved = target ?? new Doc;
- if (hasEntries) {
- let result: Opt<Field>;
- Object.keys(object).map(key => {
- // if excludeEmptyObjects is true, any qualifying conversions from toField will
- // be undefined, and thus the results that would have
- // otherwise been empty (List or Doc)s will just not be written
- if (result = toField(object[key], excludeEmptyObjects, key)) {
- resolved[key] = result;
- }
- });
- }
- title && (resolved.title = title);
- return resolved;
- }
- };
-
- /**
- * For each element in the list, recursively convert it to a document or other field
- * and push the field to the list if it is not undefined
- * @param list the list to convert
- * @returns the list mapped from JSON to field values, where each mapping
- * might involve arbitrary recursion (since toField might itself call convertList)
- */
- const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
- const target = new List();
- let result: Opt<Field>;
- // if excludeEmptyObjects is true, any qualifying conversions from toField will
- // be undefined, and thus the results that would have
- // otherwise been empty (List or Doc)s will just not be written
- list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
- if (target.length || !excludeEmptyObjects) {
- return target;
- }
- };
-
- const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
- if (data === null || data === undefined) {
- return undefined;
- }
- if (primitives.includes(typeof data)) {
- return data;
- }
- if (typeof data === "object") {
- return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
- }
- throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
- };
-
- export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
- let created: Doc | undefined;
- let layout: ((fieldKey: string) => string) | undefined;
- const field = target[fieldKey];
- const resolved = options || {};
- if (field instanceof ImageField) {
- created = Docs.Create.ImageDocument((field).url.href, resolved);
- layout = ImageBox.LayoutString;
- } else if (field instanceof Doc) {
- created = field;
- } else if (field instanceof VideoField) {
- created = Docs.Create.VideoDocument((field).url.href, resolved);
- layout = VideoBox.LayoutString;
- } else if (field instanceof PdfField) {
- created = Docs.Create.PdfDocument((field).url.href, resolved);
- layout = PDFBox.LayoutString;
- } else if (field instanceof AudioField) {
- created = Docs.Create.AudioDocument((field).url.href, resolved);
- layout = AudioBox.LayoutString;
- } else if (field instanceof InkField) {
- const { selectedColor, selectedWidth, selectedTool } = InkingControl.Instance;
- created = Docs.Create.InkDocument(selectedColor, selectedTool, selectedWidth, (field).inkData, resolved);
- layout = InkingStroke.LayoutString;
- } else if (field instanceof List && field[0] instanceof Doc) {
- created = Docs.Create.StackingDocument(DocListCast(field), resolved);
- layout = CollectionView.LayoutString;
- } else {
- created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
- layout = FormattedTextBox.LayoutString;
- }
- if (created) {
- created.layout = layout?.(fieldKey);
- created.title = fieldKey;
- proto && created.proto && (created.proto = Doc.GetProto(proto));
- }
- return created;
- }
-
- export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
- let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
- if (type.indexOf("image") !== -1) {
- ctor = Docs.Create.ImageDocument;
- if (!options._width) options._width = 300;
- }
- if (type.indexOf("video") !== -1) {
- ctor = Docs.Create.VideoDocument;
- if (!options._width) options._width = 600;
- if (!options._height) options._height = options._width * 2 / 3;
- }
- if (type.indexOf("audio") !== -1) {
- ctor = Docs.Create.AudioDocument;
- }
- if (type.indexOf("pdf") !== -1) {
- ctor = Docs.Create.PdfDocument;
- if (!options._width) options._width = 400;
- if (!options._height) options._height = options._width * 1200 / 927;
- }
- if (type.indexOf("html") !== -1) {
- if (path.includes(window.location.hostname)) {
- const s = path.split('/');
- const id = s[s.length - 1];
- return DocServer.GetRefField(id).then(field => {
- if (field instanceof Doc) {
- const alias = Doc.MakeAlias(field);
- alias.x = options.x || 0;
- alias.y = options.y || 0;
- alias._width = options._width || 300;
- alias._height = options._height || options._width || 300;
- return alias;
- }
- return undefined;
- });
- }
- ctor = Docs.Create.WebDocument;
- options = { ...options, _nativeWidth: 850, _nativeHeight: 962, _width: 500, _height: 566, title: path, };
- }
- return ctor ? ctor(path, options) : undefined;
- }
- }
}
export namespace DocUtils {
@@ -1034,6 +831,85 @@ export namespace DocUtils {
return linkDoc;
}
+
+ export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
+ let created: Doc | undefined;
+ let layout: ((fieldKey: string) => string) | undefined;
+ const field = target[fieldKey];
+ const resolved = options || {};
+ if (field instanceof ImageField) {
+ created = Docs.Create.ImageDocument((field).url.href, resolved);
+ layout = ImageBox.LayoutString;
+ } else if (field instanceof Doc) {
+ created = field;
+ } else if (field instanceof VideoField) {
+ created = Docs.Create.VideoDocument((field).url.href, resolved);
+ layout = VideoBox.LayoutString;
+ } else if (field instanceof PdfField) {
+ created = Docs.Create.PdfDocument((field).url.href, resolved);
+ layout = PDFBox.LayoutString;
+ } else if (field instanceof AudioField) {
+ created = Docs.Create.AudioDocument((field).url.href, resolved);
+ layout = AudioBox.LayoutString;
+ } else if (field instanceof InkField) {
+ created = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), (field).inkData, resolved);
+ layout = InkingStroke.LayoutString;
+ } else if (field instanceof List && field[0] instanceof Doc) {
+ created = Docs.Create.StackingDocument(DocListCast(field), resolved);
+ layout = CollectionView.LayoutString;
+ } else {
+ created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
+ layout = FormattedTextBox.LayoutString;
+ }
+ if (created) {
+ created.layout = layout?.(fieldKey);
+ created.title = fieldKey;
+ proto && created.proto && (created.proto = Doc.GetProto(proto));
+ }
+ return created;
+ }
+
+ export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
+ let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
+ if (type.indexOf("image") !== -1) {
+ ctor = Docs.Create.ImageDocument;
+ if (!options._width) options._width = 300;
+ }
+ if (type.indexOf("video") !== -1) {
+ ctor = Docs.Create.VideoDocument;
+ if (!options._width) options._width = 600;
+ if (!options._height) options._height = options._width * 2 / 3;
+ }
+ if (type.indexOf("audio") !== -1) {
+ ctor = Docs.Create.AudioDocument;
+ }
+ if (type.indexOf("pdf") !== -1) {
+ ctor = Docs.Create.PdfDocument;
+ if (!options._width) options._width = 400;
+ if (!options._height) options._height = options._width * 1200 / 927;
+ }
+ if (type.indexOf("html") !== -1) {
+ if (path.includes(window.location.hostname)) {
+ const s = path.split('/');
+ const id = s[s.length - 1];
+ return DocServer.GetRefField(id).then(field => {
+ if (field instanceof Doc) {
+ const alias = Doc.MakeAlias(field);
+ alias.x = options.x || 0;
+ alias.y = options.y || 0;
+ alias._width = options._width || 300;
+ alias._height = options._height || options._width || 300;
+ return alias;
+ }
+ return undefined;
+ });
+ }
+ ctor = Docs.Create.WebDocument;
+ options = { ...options, _nativeWidth: 850, _nativeHeight: 962, _width: 500, _height: 566, title: path, };
+ }
+ return ctor ? ctor(path, options) : undefined;
+ }
+
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void {
ContextMenu.Instance.addItem({
description: "Add Note ...",
@@ -1068,6 +944,118 @@ export namespace DocUtils {
})) as ContextMenuProps[],
icon: "eye"
});
+ }// applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
+ export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
+ const batch = UndoManager.StartBatch("makeCustomViewClicked");
+ runInAction(() => {
+ doc.layoutKey = "layout_" + templateSignature;
+ if (doc[doc.layoutKey] === undefined) {
+ createCustomView(doc, creator, templateSignature, docLayoutTemplate);
+ }
+ });
+ batch.end();
+ }
+ export function findTemplate(templateName: string, type: string, signature: string) {
+ let docLayoutTemplate: Opt<Doc>;
+ const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
+ const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
+ const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
+ const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
+ // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
+ // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+ return docLayoutTemplate;
+ }
+ export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
+ const templateName = templateSignature.replace(/\(.*\)/, "");
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature);
+
+ const customName = "layout_" + templateSignature;
+ const _width = NumCast(doc._width);
+ const _height = NumCast(doc._height);
+ const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
+
+ let fieldTemplate: Opt<Doc>;
+ if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
+ fieldTemplate = Docs.Create.TextDocument("", options);
+ } else if (doc.data instanceof PdfField) {
+ fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
+ } else if (doc.data instanceof VideoField) {
+ fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
+ } else if (doc.data instanceof AudioField) {
+ fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
+ } else if (doc.data instanceof ImageField) {
+ fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
+ }
+ const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ }
+ export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
+ Doc.setNativeView(doc);
+ if (custom) {
+ makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
+ }
+ }
+ export function iconify(doc: Doc) {
+ const layoutKey = Cast(doc.layoutKey, "string", null);
+ DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined);
+ if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
+ }
+
+ export function pileup(docList: Doc[], x?: number, y?: number) {
+ let w = 0, h = 0;
+ runInAction(() => {
+ docList.forEach(d => {
+ DocUtils.iconify(d);
+ w = Math.max(d[WidthSym](), w);
+ h = Math.max(d[HeightSym](), h);
+ });
+ h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
+ docList.forEach((d, i) => {
+ d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
+ d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
+ d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ });
+ });
+ if (x !== undefined && y !== undefined) {
+ const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+ newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+ newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+ newCollection._width = newCollection._height = 110;
+ //newCollection.borderRounding = "40px";
+ newCollection._jitterRotation = 10;
+ newCollection._backgroundColor = "gray";
+ newCollection._overflow = "visible";
+ return newCollection;
+ }
+ }
+
+ export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) {
+ let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
+ if (!(optionsCollection instanceof Doc)) {
+ optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set` }, enumeratedFieldKey);
+ Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
+ }
+ const options = optionsCollection as Doc;
+ const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
+ const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
+ targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
+ targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
+ targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
+ enumerations.map(enumeration => {
+ const found = DocListCast(options.data).find(d => d.title === enumeration.title);
+ if (found) {
+ found._backgroundColor = enumeration._backgroundColor || found._backgroundColor;
+ found._color = enumeration.color || found._color;
+ } else {
+ Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, enumeration));
+ }
+ });
+ return optionsCollection;
}
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 496099557..b0cea9947 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,7 +2,7 @@ import { computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
-import { Docs, DocumentOptions } from "../documents/Documents";
+import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
import { UndoManager } from "./UndoManager";
import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
import { List } from "../../fields/List";
@@ -11,7 +11,6 @@ import { ScriptField, ComputedField } from "../../fields/ScriptField";
import { Cast, PromiseValue, StrCast, NumCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { DragManager } from "./DragManager";
-import { InkingControl } from "../views/InkingControl";
import { Scripting } from "./Scripting";
import { CollectionViewType } from "../views/collections/CollectionView";
import { makeTemplate } from "./DropConverter";
@@ -32,7 +31,6 @@ export class CurrentUserUtils {
public static get MainDocId() { return this.mainDocId; }
public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
@computed public static get UserDocument() { return Doc.UserDoc(); }
- @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).inkPen as Doc; }
@observable public static GuestTarget: Doc | undefined;
@observable public static GuestWorkspace: Doc | undefined;
@@ -221,7 +219,7 @@ export class CurrentUserUtils {
];
if (doc.fieldTypes === undefined) {
doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" });
- Doc.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues);
+ DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues);
}
if (doc["template-notes"] === undefined) {
@@ -301,7 +299,7 @@ export class CurrentUserUtils {
static creatorBtnDescriptors(doc: Doc): {
title: string, label: string, icon: string, drag?: string, ignoreClick?: boolean,
- click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc
+ click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc
}[] {
if (doc.emptyPresentation === undefined) {
doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
@@ -334,11 +332,11 @@ export class CurrentUserUtils {
{ title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
{ title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
{ title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
- // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc },
- // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc },
+ // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
+ // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
{ title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
{ title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
{ title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
@@ -358,7 +356,7 @@ export class CurrentUserUtils {
}
}
const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
- const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
+ const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
_nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
icon,
title,
@@ -368,7 +366,7 @@ export class CurrentUserUtils {
onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
onClick: click ? ScriptField.MakeScript(click) : undefined,
ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined,
- activePen,
+ activeInkPen,
backgroundColor,
removeDropProperties: new List<string>(["dropAction"]),
dragFactory,
@@ -387,31 +385,31 @@ export class CurrentUserUtils {
}
static setupMobileButtons(doc: Doc, buttons?: string[]) {
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+ const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
{ title: "record", icon: "microphone", ignoreClick: true, click: "FILL" },
- { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc },
- { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc },
- // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "red", activePen: doc },
+ { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
+ { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
+ // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "red", activeInkPen: doc },
{ title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" },
// { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" },
];
return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
_nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen,
+ ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activeInkPen: data.activeInkPen,
backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
}));
}
static setupThumbButtons(doc: Doc) {
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
- { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
- { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
+ const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+ { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
+ { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
];
return docProtoData.map(data => Docs.Create.FontIconDocument({
_nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon,
@@ -419,7 +417,7 @@ export class CurrentUserUtils {
onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
clipboard: data.clipboard,
onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
- ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true,
+ ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activeInkPen: data.activeInkPen, pointerHack: true,
backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
}));
}
@@ -612,8 +610,8 @@ export class CurrentUserUtils {
static setupDockedButtons(doc: Doc) {
if (doc["dockedBtn-pen"] === undefined) {
doc["dockedBtn-pen"] = CurrentUserUtils.ficon({
- onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this, this.inkWidth, this.backgroundColor)"),
- author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activePen.inkPen, this)`), activePen: doc
+ onClick: ScriptField.MakeScript("activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)"),
+ author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activeInkPen, this)`), activeInkPen: doc
});
}
if (doc["dockedBtn-undo"] === undefined) {
@@ -688,10 +686,11 @@ export class CurrentUserUtils {
}
static async updateUserDocument(doc: Doc) {
- new InkingControl();
doc.title = Doc.CurrentUserEmail;
- doc.activePen = doc;
- doc.inkColor = StrCast(doc.backgroundColor, "rgb(0, 0, 0)");
+ doc.activeInkPen = doc;
+ doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
+ doc.activeInkWidth = StrCast(doc.activeInkWidth, "1");
+ doc.activeInkBezier = StrCast(doc.activeInkBezier, "");
doc.fontSize = NumCast(doc.fontSize, 12);
doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ab087335e..78c05f572 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -126,13 +126,13 @@ export class DocumentManager {
finished?.();
}
public jumpToDocument = async (
- targetDoc: Doc,
- willZoom: boolean,
- createViewFunc = DocumentManager.addRightSplit,
- docContext?: Doc,
- linkId?: string,
- closeContextIfNotFound: boolean = false,
- originatingDoc: Opt<Doc> = undefined,
+ targetDoc: Doc, // document to display
+ willZoom: boolean, // whether to zoom doc to take up most of screen
+ createViewFunc = DocumentManager.addRightSplit, // how to create a view of the doc if it doesn't exist
+ docContext?: Doc, // context to load that should contain the target
+ linkId?: string, // link that's being followed
+ closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
+ originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
finished?: () => void
): Promise<void> => {
const getFirstDocView = DocumentManager.Instance.getFirstDocumentView;
@@ -168,7 +168,7 @@ export class DocumentManager {
highlight();
} else { // otherwise try to get a view of the context of the target
const targetDocContextView = getFirstDocView(targetDocContext);
- targetDocContext.scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling
+ targetDocContext._scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling
if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
targetDocContext.panTransformType = "Ease";
targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2a9c1633a..e7a14f9c5 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -210,7 +210,7 @@ export namespace DragManager {
e.docDragData && (e.docDragData.droppedDocuments =
dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
- dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeClone(d) : d)
+ dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeDelegate(d) : d)
);
e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
(dragData?.removeDropProperties || []).concat(Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), [])).map(prop => drop[prop] = undefined)
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 1e8f07049..4e5cde558 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -123,10 +123,10 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
}
const { accessPaths, exifData } = result;
const path = Utils.prepend(accessPaths.agnostic.client);
- const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
+ const document = await Doc.Get.DocumentFromType(type, path, { _width: 300, title: name });
const { data, error } = exifData;
if (document) {
- Doc.GetProto(document).exif = error || Docs.Get.FromJson({ data });
+ Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data });
docs.push(document);
}
}));
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 072e5f58a..0d12b39b8 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -20,7 +20,7 @@ export namespace ImageUtils {
nativeHeight,
exifData: { error, data }
} = await Networking.PostToServer("/inspectImage", { source });
- document.exif = error || Docs.Get.FromJson({ data });
+ document.exif = error || Doc.Get.FromJson({ data });
const proto = Doc.GetProto(document);
proto["data-nativeWidth"] = nativeWidth;
proto["data-nativeHeight"] = nativeHeight;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 3a5345c80..3948611f0 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,4 +1,6 @@
import React = require("react");
+import * as beziercurve from 'bezier-curve';
+import * as fitCurve from 'fit-curve';
export namespace InteractionUtils {
export const MOUSETYPE = "mouse";
@@ -87,8 +89,33 @@ export namespace InteractionUtils {
return myTouches;
}
- export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string) {
- const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
+ export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string, bezier: string, scalex: number, scaley: number, shape: string) {
+ var pts = "";
+ if (shape) {
+ //if any of the shape are true
+ const shapePts = makePolygon(shape, points);
+ pts = shapePts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
+ }
+ else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
+ //pointer is up (first and last points are the same)
+ const newPoints: number[][] = [];
+ const newPts: { X: number; Y: number; }[] = [];
+ //convert to [][] for fitcurve module
+ for (var i = 0; i < points.length - 1; i++) {
+ newPoints.push([points[i].X, points[i].Y]);
+ }
+ const bezierCurves = fitCurve(newPoints, parseInt(bezier));
+ for (var i = 0; i < bezierCurves.length; i++) {
+ for (var t = 0; t < 1; t += 0.01) {
+ const point = beziercurve(t, bezierCurves[i]);
+ newPts.push({ X: point[0], Y: point[1] });
+ }
+ }
+ pts = newPts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
+ } else {
+ //in the middle of drawing
+ pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
+ }
return (
<polyline
points={pts}
@@ -103,6 +130,101 @@ export namespace InteractionUtils {
);
}
+ export function makePolygon(shape: string, points: { X: number, Y: number }[]) {
+ if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
+ //pointer is up (first and last points are the same)
+ if (shape === "arrow" || shape === "line") {
+ //if arrow or line, the two end points should be the starting and the ending point
+ var left = points[0].X;
+ var top = points[0].Y;
+ var right = points[1].X;
+ var bottom = points[1].Y;
+ } else {
+ //otherwise take max and min
+ const xs = points.map(p => p.X);
+ const ys = points.map(p => p.Y);
+ right = Math.max(...xs);
+ left = Math.min(...xs);
+ bottom = Math.max(...ys);
+ top = Math.min(...ys);
+ }
+ } else {
+ //if in the middle of drawing
+ //take first and last points
+ right = points[points.length - 1].X;
+ left = points[0].X;
+ bottom = points[points.length - 1].Y;
+ top = points[0].Y;
+ if (shape !== "arrow" && shape !== "line") {
+ //switch left/right and top/bottom if needed
+ if (left > right) {
+ const temp = right;
+ right = left;
+ left = temp;
+ }
+ if (top > bottom) {
+ const temp = top;
+ top = bottom;
+ bottom = temp;
+ }
+ }
+ }
+ points = [];
+ switch (shape) {
+ case "rectangle":
+ points.push({ X: left, Y: top });
+ points.push({ X: right, Y: top });
+ points.push({ X: right, Y: bottom });
+ points.push({ X: left, Y: bottom });
+ points.push({ X: left, Y: top });
+ return points;
+ case "triangle":
+ points.push({ X: left, Y: bottom });
+ points.push({ X: right, Y: bottom });
+ points.push({ X: (right + left) / 2, Y: top });
+ points.push({ X: left, Y: bottom });
+ return points;
+ case "circle":
+ const centerX = (right + left) / 2;
+ const centerY = (bottom + top) / 2;
+ const radius = bottom - centerY;
+ for (var y = top; y < bottom; y++) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ points.push({ X: x, Y: y });
+ }
+ for (var y = bottom; y > top; y--) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ const newX = centerX - (x - centerX);
+ points.push({ X: newX, Y: y });
+ }
+ points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
+ return points;
+ case "arrow":
+ const x1 = left;
+ const y1 = top;
+ const x2 = right;
+ const y2 = bottom;
+ const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2)));
+ const L2 = L1 / 5;
+ const angle = 0.785398;
+ const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
+ const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
+ const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
+ const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
+ points.push({ X: x1, Y: y1 });
+ points.push({ X: x2, Y: y2 });
+ points.push({ X: x3, Y: y3 });
+ points.push({ X: x4, Y: y4 });
+ points.push({ X: x2, Y: y2 });
+ return points;
+ case "line":
+ points.push({ X: left, Y: top });
+ points.push({ X: right, Y: bottom });
+ return points;
+ default:
+ return points;
+ }
+ }
/**
* Returns whether or not the pointer event passed in is of the type passed in
* @param e - pointer event. this event could be from a mouse, a pen, or a finger
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 8e6ccf098..95528e25a 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -2,10 +2,8 @@ import { Doc, DocListCast } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
-import { Docs } from "../documents/Documents";
import { Scripting } from "./Scripting";
-
/*
* link doc:
* - anchor1: doc
@@ -34,16 +32,12 @@ export class LinkManager {
// the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes'
// lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type
public get LinkManagerDoc(): Doc | undefined {
- return Docs.Prototypes.MainLinkDocument();
+ return Doc.UserDoc().globalLinkDatabase as Doc;
}
public getAllLinks(): Doc[] {
const ldoc = LinkManager.Instance.LinkManagerDoc;
- if (ldoc) {
- const docs = DocListCast(ldoc.data);
- return docs;
- }
- return [];
+ return ldoc ? DocListCast(ldoc.data) : [];
}
public addLink(linkDoc: Doc): boolean {
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index f810361c6..9a6121d20 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,5 +1,4 @@
import React = require("react");
-import { observer } from "mobx-react";
import { observable, action } from "mobx";
import "./AntimodeMenu.scss";
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 1ba9fcc32..3af570f1e 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -2,8 +2,6 @@ import { Doc, Opt, DataSym, DocListCast } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
-import { listSpec } from '../../fields/Schema';
-import { InkingControl } from './InkingControl';
import { InkTool } from '../../fields/InkField';
import { InteractionUtils } from '../util/InteractionUtils';
import { List } from '../../fields/List';
@@ -59,7 +57,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
- active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
+ active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);// && !Doc.SelectedTool(); // bcz: inking state shouldn't affect static tools
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
@@ -148,9 +146,9 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
}
whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
- active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
+ active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.props.Document.isBackground) &&
(this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0 || BoolCast((this.layoutDoc as any).forceActive)) ? true : false)
- annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
+ annotationsActive = (outsideReaction?: boolean) => (Doc.GetSelectedTool() !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
(this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
}
return Component;
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a4d4af2f0..0a96b058b 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -152,6 +152,11 @@ $linkGap : 3px;
text-align: center;
display: flex;
border-bottom: solid 1px;
+ margin-left:10px;
+ width: calc(100% - 10px);
+ }
+ .focus-visible {
+ margin-left:0px;
}
.publishBox {
width: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 04f02c683..e7a237e62 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -466,7 +466,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const titleArea = this._edtingTitle ?
<>
- <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle} style={{ width: minimal ? "100%" : "calc(100% - 20px)" }}
+ <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} />
{minimal ? (null) : <div className="publishBox" title="make document referenceable by its title"
onPointerDown={action(e => {
@@ -485,7 +485,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<FontAwesomeIcon size="lg" icon="cog" />
</div>}
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
- <span style={{ width: "calc(100% - 25px)", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
+ <span style={{ width: "100%", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
</div>
</>;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 4352ac52c..3384b5fce 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,43 +1,35 @@
import React = require("react");
-import { Touchable } from "./Touchable";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import "./GestureOverlay.scss";
-import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow, trace } from "mobx";
+import { Doc } from "../../fields/Doc";
+import { InkData, InkTool } from "../../fields/InkField";
+import { Cast, FieldValue, NumCast } from "../../fields/Types";
+import MobileInkOverlay from "../../mobile/MobileInkOverlay";
+import MobileInterface from "../../mobile/MobileInterface";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { MobileInkOverlayContent } from "../../server/Message";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from "../../Utils";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { DocServer } from "../DocServer";
+import { DocUtils } from "../documents/Documents";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
-import { InkingControl } from "./InkingControl";
-import { InkTool, InkData } from "../../fields/InkField";
-import { Doc } from "../../fields/Doc";
import { LinkManager } from "../util/LinkManager";
-import { DocUtils, Docs } from "../documents/Documents";
-import { undoBatch } from "../util/UndoManager";
import { Scripting } from "../util/Scripting";
-import { FieldValue, Cast, NumCast, BoolCast } from "../../fields/Types";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import HorizontalPalette from "./Palette";
-import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils";
-import { DocumentView } from "./nodes/DocumentView";
import { Transform } from "../util/Transform";
-import { DocumentContentsView } from "./nodes/DocumentContentsView";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { DocServer } from "../DocServer";
-import htmlToImage from "html-to-image";
-import { ScriptField } from "../../fields/ScriptField";
-import { listSpec } from "../../fields/Schema";
-import { List } from "../../fields/List";
-import { CollectionViewType } from "./collections/CollectionView";
-import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import MobileInterface from "../../mobile/MobileInterface";
-import { MobileInkOverlayContent } from "../../server/Message";
-import MobileInkOverlay from "../../mobile/MobileInkOverlay";
+import "./GestureOverlay.scss";
+import { ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
+import { DocumentView } from "./nodes/DocumentView";
import { RadialMenu } from "./nodes/RadialMenu";
-import { SelectionManager } from "../util/SelectionManager";
-
+import HorizontalPalette from "./Palette";
+import { Touchable } from "./Touchable";
+import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
@observer
export default class GestureOverlay extends Touchable {
static Instance: GestureOverlay;
+ @observable public InkShape: string = "";
@observable public SavedColor?: string;
@observable public SavedWidth?: string;
@observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -499,7 +491,7 @@ export default class GestureOverlay extends Touchable {
@action
onPointerDown = (e: React.PointerEvent) => {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
this._points.push({ X: e.clientX, Y: e.clientY });
e.stopPropagation();
e.preventDefault();
@@ -513,7 +505,7 @@ export default class GestureOverlay extends Touchable {
@action
onPointerMove = (e: PointerEvent) => {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
this._points.push({ X: e.clientX, Y: e.clientY });
e.stopPropagation();
e.preventDefault();
@@ -581,14 +573,14 @@ export default class GestureOverlay extends Touchable {
if (this._points.length > 1) {
const B = this.svgBounds;
const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
-
+ //push first points to so interactionUtil knows pointer is up
+ this._points.push({ X: this._points[0].X, Y: this._points[0].Y });
if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) {
- const { selectedColor, selectedWidth } = InkingControl.Instance;
DocServer.Mobile.dispatchGesturePoints({
points: this._points,
bounds: B,
- color: selectedColor,
- width: selectedWidth
+ color: ActiveInkColor(),
+ width: ActiveInkWidth()
});
}
@@ -630,6 +622,13 @@ export default class GestureOverlay extends Touchable {
break;
}
}
+ //if any of the shape is activated in the InkOptionsMenu
+ else if (this.InkShape) {
+ this.makePolygon(this.InkShape, false);
+ this.dispatchGesture(GestureUtils.Gestures.Stroke);
+ this._points = [];
+ this.InkShape = "";
+ }
// if we're not drawing in a toolglass try to recognize as gesture
else {
const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
@@ -651,6 +650,15 @@ export default class GestureOverlay extends Touchable {
case GestureUtils.Gestures.Line:
actionPerformed = this.handleLineGesture();
break;
+ case GestureUtils.Gestures.Triangle:
+ this.makePolygon("triangle", true);
+ break;
+ case GestureUtils.Gestures.Circle:
+ this.makePolygon("circle", true);
+ break;
+ case GestureUtils.Gestures.Rectangle:
+ this.makePolygon("rectangle", true);
+ break;
case GestureUtils.Gestures.Scribble:
console.log("scribble");
break;
@@ -671,6 +679,95 @@ export default class GestureOverlay extends Touchable {
document.removeEventListener("pointerup", this.onPointerUp);
}
+ makePolygon = (shape: string, gesture: boolean) => {
+ const xs = this._points.map(p => p.X);
+ const ys = this._points.map(p => p.Y);
+ var right = Math.max(...xs);
+ var left = Math.min(...xs);
+ var bottom = Math.max(...ys);
+ var top = Math.min(...ys);
+
+ if (!gesture) {
+ //if shape options is activated in inkOptionMenu
+ //take second to last point because _point[length-1] is _points[0]
+ right = this._points[this._points.length - 2].X;
+ left = this._points[0].X;
+ bottom = this._points[this._points.length - 2].Y;
+ top = this._points[0].Y;
+ if (shape !== "arrow" && shape !== "line") {
+ if (left > right) {
+ const temp = right;
+ right = left;
+ left = temp;
+ }
+ if (top > bottom) {
+ const temp = top;
+ top = bottom;
+ bottom = temp;
+ }
+ }
+ }
+ this._points = [];
+ switch (shape) {
+ //must push an extra point in the end so InteractionUtils knows pointer is up.
+ //must be (points[0].X,points[0]-1)
+ case "rectangle":
+ this._points.push({ X: left, Y: top });
+ this._points.push({ X: right, Y: top });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: top });
+ this._points.push({ X: left, Y: top - 1 });
+ break;
+ case "triangle":
+ this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: (right + left) / 2, Y: top });
+ this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom - 1 });
+ break;
+ case "circle":
+ const centerX = (right + left) / 2;
+ const centerY = (bottom + top) / 2;
+ const radius = bottom - centerY;
+ for (var y = top; y < bottom; y++) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ this._points.push({ X: x, Y: y });
+ }
+ for (var y = bottom; y > top; y--) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ const newX = centerX - (x - centerX);
+ this._points.push({ X: newX, Y: y });
+ }
+ this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
+ this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 });
+ break;
+ case "line":
+ this._points.push({ X: left, Y: top });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom - 1 });
+ break;
+ case "arrow":
+ const x1 = left;
+ const y1 = top;
+ const x2 = right;
+ const y2 = bottom;
+ const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2)));
+ const L2 = L1 / 5;
+ const angle = 0.785398;
+ const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
+ const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
+ const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
+ const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
+ this._points.push({ X: x1, Y: y1 });
+ this._points.push({ X: x2, Y: y2 });
+ this._points.push({ X: x3, Y: y3 });
+ this._points.push({ X: x4, Y: y4 });
+ this._points.push({ X: x2, Y: y2 });
+ this._points.push({ X: x1, Y: y1 - 1 });
+ }
+ }
+
dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
target?.dispatchEvent(
@@ -710,11 +807,11 @@ export default class GestureOverlay extends Touchable {
[this._strokes.map(l => {
const b = this.getBounds(l);
return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(l, b.left, b.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth)}
+ {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)}
</svg>;
}),
this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points, B.left, B.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth)}
+ {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)}
</svg>]
];
}
@@ -804,16 +901,16 @@ Scripting.addGlobal(function setToolglass(tool: any) {
});
Scripting.addGlobal(function setPen(width: any, color: any) {
runInAction(() => {
- GestureOverlay.Instance.SavedColor = InkingControl.Instance.selectedColor;
- InkingControl.Instance.updateSelectedColor(color);
- GestureOverlay.Instance.SavedWidth = InkingControl.Instance.selectedWidth;
- InkingControl.Instance.switchWidth(width);
+ GestureOverlay.Instance.SavedColor = ActiveInkColor();
+ SetActiveInkColor(color);
+ GestureOverlay.Instance.SavedWidth = ActiveInkWidth();
+ SetActiveInkWidth(width);
});
});
Scripting.addGlobal(function resetPen() {
runInAction(() => {
- InkingControl.Instance.updateSelectedColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
- InkingControl.Instance.switchWidth(GestureOverlay.Instance.SavedWidth ?? "2");
+ SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
+ SetActiveInkWidth(GestureOverlay.Instance.SavedWidth ?? "2");
});
});
Scripting.addGlobal(function createText(text: any, x: any, y: any) {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 255142771..2d5af5386 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,26 +1,25 @@
-import { UndoManager, undoBatch } from "../util/UndoManager";
-import { SelectionManager } from "../util/SelectionManager";
-import { CollectionDockingView } from "./collections/CollectionDockingView";
-import { MainView } from "./MainView";
-import { DragManager } from "../util/DragManager";
-import { action, runInAction } from "mobx";
+import { action } from "mobx";
+import { DateField } from "../../fields/DateField";
import { Doc, DocListCast } from "../../fields/Doc";
-import { DictationManager } from "../util/DictationManager";
-import SharingManager from "../util/SharingManager";
-import { Cast, PromiseValue, NumCast } from "../../fields/Types";
-import { ScriptField } from "../../fields/ScriptField";
-import { InkingControl } from "./InkingControl";
+import { Id } from "../../fields/FieldSymbols";
import { InkTool } from "../../fields/InkField";
-import { DocumentView } from "./nodes/DocumentView";
+import { List } from "../../fields/List";
+import { ScriptField } from "../../fields/ScriptField";
+import { Cast, PromiseValue } from "../../fields/Types";
import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
-import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
+import { DocServer } from "../DocServer";
+import { DocumentType } from "../documents/DocumentTypes";
+import { DictationManager } from "../util/DictationManager";
+import { DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
+import SharingManager from "../util/SharingManager";
+import { undoBatch, UndoManager } from "../util/UndoManager";
+import { CollectionDockingView } from "./collections/CollectionDockingView";
import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
-import { Id } from "../../fields/FieldSymbols";
import { DocumentDecorations } from "./DocumentDecorations";
-import { DocumentType } from "../documents/DocumentTypes";
-import { DocServer } from "../DocServer";
-import { List } from "../../fields/List";
-import { DateField } from "../../fields/DateField";
+import { InkingStroke } from "./InkingStroke";
+import { MainView } from "./MainView";
+import { DocumentView } from "./nodes/DocumentView";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -78,7 +77,7 @@ export default class KeyManager {
break;
case "escape":
const main = MainView.Instance;
- InkingControl.Instance.switchTool(InkTool.None);
+ Doc.SetSelectedTool(InkTool.None);
if (main.isPointerDown) {
DragManager.AbortDrag();
} else {
diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss
deleted file mode 100644
index 465e14d07..000000000
--- a/src/client/views/InkingControl.scss
+++ /dev/null
@@ -1,131 +0,0 @@
-@import "globalCssVariables";
-.inking-control {
- bottom: 20px;
- margin: 0;
- padding: 0;
- display: flex;
- label,
- input,
- option {
- font-size: 12px;
- }
- input[type="range"] {
- -webkit-appearance: none;
- background-color: transparent;
- vertical-align: middle;
- margin-top: 8px;
- &:focus {
- outline: none;
- }
- &::-webkit-slider-runnable-track {
- width: 100%;
- height: 3px;
- border-radius: 1.5px;
- cursor: pointer;
- background: $intermediate-color;
- }
- &::-webkit-slider-thumb {
- height: 12px;
- width: 12px;
- border: 1px solid $intermediate-color;
- border-radius: 6px;
- background: $light-color;
- cursor: pointer;
- -webkit-appearance: none;
- margin-top: -4px;
- }
- &::-moz-range-track {
- width: 100%;
- height: 3px;
- border-radius: 1.5px;
- cursor: pointer;
- background: $light-color;
- }
- &::-moz-range-thumb {
- height: 12px;
- width: 12px;
- border: 1px solid $intermediate-color;
- border-radius: 6px;
- background: $light-color;
- cursor: pointer;
- -webkit-appearance: none;
- margin-top: -4px;
- }
- }
- input[type="text"] {
- border: none;
- padding: 0 0px;
- background: transparent;
- color: $dark-color;
- font-size: 12px;
- margin-top: 4px;
- }
- .ink-panel {
- height: 24px;
- vertical-align: middle;
- line-height: 28px;
- padding: 0 10px;
- color: $intermediate-color;
- &:first {
- margin-top: 0;
- }
- }
- .ink-tools {
- display: flex;
- background-color: transparent;
- border-radius: 0;
- padding: 0;
- button {
- height: 36px;
- padding: 0px;
- padding-bottom: 3px;
- margin-left: 10px;
- background-color: transparent;
- color: $intermediate-color;
- }
- button:hover {
- transform: scale(1.15);
- }
- }
- .ink-size {
- display: flex;
- justify-content: space-between;
- input[type="text"] {
- width: 42px;
- }
- >* {
- margin-right: 6px;
- &:last-child {
- margin-right: 0;
- }
- }
- }
- .ink-color {
- display: flex;
- position: relative;
- padding-right: 0;
- label {
- margin-right: 6px;
- }
- .ink-color-display {
- border-radius: 11px;
- width: 22px;
- height: 22px;
- cursor: pointer;
- text-align: center; // span {
- // color: $light-color;
- // font-size: 8px;
- // user-select: none;
- // }
- }
- .ink-color-picker {
- background-color: $light-color;
- border-radius: 5px;
- padding: 12px;
- position: absolute;
- bottom: 36px;
- left: -3px;
- box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
- }
- }
-} \ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
deleted file mode 100644
index 41ee36d05..000000000
--- a/src/client/views/InkingControl.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { action, computed, observable } from "mobx";
-import { ColorState } from 'react-color';
-import { Doc } from "../../fields/Doc";
-import { InkTool } from "../../fields/InkField";
-import { FieldValue, NumCast, StrCast } from "../../fields/Types";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { Scripting } from "../util/Scripting";
-import { SelectionManager } from "../util/SelectionManager";
-import { undoBatch } from "../util/UndoManager";
-import GestureOverlay from "./GestureOverlay";
-import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
-
-export class InkingControl {
- @observable static Instance: InkingControl;
- @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(Doc.UserDoc().inkTool)) ?? InkTool.None; }
- @computed private get _selectedColor(): string { return CurrentUserUtils.ActivePen ? FieldValue(StrCast(CurrentUserUtils.ActivePen.backgroundColor)) ?? "rgb(0, 0, 0)" : "rgb(0, 0, 0)"; }
- @computed private get _selectedWidth(): string { return FieldValue(StrCast(Doc.UserDoc().inkWidth)) ?? "2"; }
- @observable public _open: boolean = false;
-
- constructor() {
- InkingControl.Instance = this;
- }
-
- switchTool = action((tool: InkTool): void => {
- // this._selectedTool = tool;
- Doc.UserDoc().inkTool = tool;
- });
- decimalToHexString(number: number) {
- if (number < 0) {
- number = 0xFFFFFFFF + number + 1;
- }
- return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
- }
-
- @undoBatch
- switchColor = action((color: ColorState): void => {
- Doc.UserDoc().backgroundColor = color.hex.startsWith("#") ?
- color.hex + (color.rgb.a ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex;
- CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = color.hex);
-
- if (InkingControl.Instance.selectedTool === InkTool.None) {
- const selected = SelectionManager.SelectedDocuments();
- selected.map(view => {
- const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
- view.props.Document.layout instanceof Doc ? view.props.Document.layout :
- view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
- if (targetDoc) {
- if (StrCast(Doc.Layout(view.props.Document).layout).indexOf("FormattedTextBox") !== -1 && FormattedTextBox.HadSelection) {
- Doc.Layout(view.props.Document).color = Doc.UserDoc().bacgroundColor;
- } else {
- Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
- }
- }
- });
- }
- });
- @action
- switchWidth = (width: string): void => {
- // this._selectedWidth = width;
- if (!isNaN(parseInt(width))) {
- Doc.UserDoc().inkWidth = width;
- }
- }
-
- @computed
- get selectedTool() {
- return this._selectedTool;
- }
-
- @computed
- get selectedColor() {
- return this._selectedColor;
- }
-
- @action
- updateSelectedColor(value: string) {
- // this._selectedColor = value;
- Doc.UserDoc().inkColor = value;
- }
-
- @computed
- get selectedWidth() {
- return this._selectedWidth;
- }
-
-}
-Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
-Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
-Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
-Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); });
-Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
-Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
-Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); }); \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8938e8b6c..b545ede54 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,19 +1,21 @@
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
+import { observable, runInAction, action } from "mobx";
import { observer } from "mobx-react";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
import { makeInterface } from "../../fields/Schema";
-import { Cast, StrCast, NumCast } from "../../fields/Types";
+import { Cast, StrCast } from "../../fields/Types";
+import { TraceMobx } from "../../fields/util";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
-import { InkingControl } from "./InkingControl";
import "./InkingStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
-import { TraceMobx } from "../../fields/util";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { ContextMenu } from "./ContextMenu";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
-import { library } from "@fortawesome/fontawesome-svg-core";
+import { Scripting } from "../util/Scripting";
+import { Doc } from "../../fields/Doc";
library.add(faPaintBrush);
@@ -38,21 +40,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const top = Math.min(...ys);
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- const points = InteractionUtils.CreatePolyline(data, left, top,
- StrCast(this.layoutDoc.color, InkingControl.Instance.selectedColor),
- StrCast(this.layoutDoc.strokeWidth, InkingControl.Instance.selectedWidth));
const width = right - left;
const height = bottom - top;
const scaleX = this.props.PanelWidth() / width;
const scaleY = this.props.PanelHeight() / height;
+ const points = InteractionUtils.CreatePolyline(data, left, top,
+ StrCast(this.layoutDoc.color, ActiveInkColor()),
+ StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()),
+ StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), scaleX, scaleY, "");
return (
<svg className="inkingStroke"
width={width}
height={height}
- style={{
- transform: `scale(${scaleX}, ${scaleY})`,
- mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
- }}
+ style={{ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset" }}
onContextMenu={() => {
ContextMenu.Instance.addItem({
description: "Analyze Stroke",
@@ -65,4 +65,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
</svg>
);
}
-} \ No newline at end of file
+}
+
+
+export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
+export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
+export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
+export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); }
+export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
+export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
+export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) {
+ Doc.SetSelectedTool(pen ? InkTool.Highlighter : InkTool.None);
+ SetActiveInkWidth(width);
+ SetActiveInkColor(color);
+});
+Scripting.addGlobal(function activateEraser(pen: any) { return Doc.SetSelectedTool(pen ? InkTool.Eraser : InkTool.None); });
+Scripting.addGlobal(function activateStamp(pen: any) { return Doc.SetSelectedTool(pen ? InkTool.Stamp : InkTool.None); });
+Scripting.addGlobal(function deactivateInk() { return Doc.SetSelectedTool(InkTool.None); });
+Scripting.addGlobal(function setInkWidth(width: any) { return Doc.SetSelectedTool(width); });
+Scripting.addGlobal(function setInkColor(color: any) { return Doc.SetSelectedTool(color); }); \ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 17c001971..6878658a8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,12 +5,16 @@ import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { DocServer } from "../DocServer";
import { AssignAllExtensions } from "../../extensions/General/Extensions";
+import { Networking } from "../Network";
AssignAllExtensions();
+export let resolvedPorts: { server: number, socket: number };
+
(async () => {
const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
+ resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts"));
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email);
await Docs.Prototypes.initialize();
if (info.id !== "__guest__") {
// a guest will not have an id registered
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b7a75dfb9..3677746cd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -33,6 +33,7 @@ import SharingManager from '../util/SharingManager';
import { Transform } from '../util/Transform';
import { CollectionDockingView } from './collections/CollectionDockingView';
import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import InkOptionsMenu from './collections/collectionFreeForm/InkOptionsMenu';
import { CollectionLinearView } from './collections/CollectionLinearView';
import { CollectionView, CollectionViewType } from './collections/CollectionView';
import { ContextMenu } from './ContextMenu';
@@ -51,6 +52,8 @@ import { PreviewCursor } from './PreviewCursor';
import { ScriptField } from '../../fields/ScriptField';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { SnappingManager } from '../util/SnappingManager';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { DocumentManager } from '../util/DocumentManager';
@observer
export class MainView extends React.Component {
@@ -83,6 +86,14 @@ export class MainView extends React.Component {
window.removeEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("paste", KeyManager.Instance.paste as any);
+ document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on
+ const id = FormattedTextBox.GetDocFromUrl(e.detail);
+ DocServer.GetRefField(id).then(doc => {
+ if (doc instanceof Doc) {
+ DocumentManager.Instance.jumpToDocument(doc, false, undefined);
+ }
+ });
+ });
}
componentWillUnMount() {
@@ -139,10 +150,7 @@ export class MainView extends React.Component {
initEventListeners = () => {
window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
- window.addEventListener("dragover", (e) => {
- console.log("MDRAG");
- e.preventDefault();
- }, false); // drag event handler
+ window.addEventListener("dragover", (e) => { e.preventDefault(); }, false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", this.globalPointerDown);
document.addEventListener("pointerup", this.globalPointerUp);
@@ -246,7 +254,6 @@ export class MainView extends React.Component {
onDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
- console.log("Drop");
}
@action
@@ -538,7 +545,7 @@ export class MainView extends React.Component {
}
@computed get snapLines() {
- return <div className="mainView-snapLines">
+ return !Doc.UserDoc().showSnapLines ? (null) : <div className="mainView-snapLines">
<svg style={{ width: "100%", height: "100%" }}>
{SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
{SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
@@ -561,6 +568,7 @@ export class MainView extends React.Component {
<RadialMenu />
<PDFMenu />
<MarqueeOptionsMenu />
+ <InkOptionsMenu />
<RichTextMenu />
<OverlayView />
<TimelineMenu />
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 10d023d83..5e48d5ffb 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -1,8 +1,6 @@
import * as React from 'react';
import { action } from 'mobx';
import { InteractionUtils } from '../util/InteractionUtils';
-import { SelectionManager } from '../util/SelectionManager';
-import { RadialMenu } from './nodes/RadialMenu';
const HOLD_DURATION = 1000;
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 92b0f05b3..b562bd957 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -12,11 +12,9 @@ import { Transform } from "../../util/Transform";
import { TimelineMenu } from "./TimelineMenu";
import { Docs } from "../../documents/Documents";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
import { emptyPath } from "../../../Utils";
-
/**
* Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
*/
@@ -100,16 +98,11 @@ export namespace KeyframeFunc {
export const convertPixelTime = (pos: number, unit: "mili" | "sec" | "min" | "hr", dir: "pixel" | "time", tickSpacing: number, tickIncrement: number) => {
const time = dir === "pixel" ? (pos * tickSpacing) / tickIncrement : (pos / tickSpacing) * tickIncrement;
switch (unit) {
- case "mili":
- return time;
- case "sec":
- return dir === "pixel" ? time / 1000 : time * 1000;
- case "min":
- return dir === "pixel" ? time / 60000 : time * 60000;
- case "hr":
- return dir === "pixel" ? time / 3600000 : time * 3600000;
- default:
- return time;
+ case "mili": return time;
+ case "sec": return dir === "pixel" ? time / 1000 : time * 1000;
+ case "min": return dir === "pixel" ? time / 60000 : time * 60000;
+ case "hr": return dir === "pixel" ? time / 3600000 : time * 3600000;
+ default: return time;
}
};
}
@@ -187,10 +180,10 @@ export class Keyframe extends React.Component<IProps> {
const fadeIn = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade);
const fadeOut = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, KeyframeFunc.KeyframeType.fade);
const finish = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration, KeyframeFunc.KeyframeType.end);
- (fadeIn.key as Doc).opacity = 1;
- (fadeOut.key as Doc).opacity = 1;
- (start.key as Doc).opacity = 0.1;
- (finish.key as Doc).opacity = 0.1;
+ (fadeIn as Doc).opacity = 1;
+ (fadeOut as Doc).opacity = 1;
+ (start as Doc).opacity = 0.1;
+ (finish as Doc).opacity = 0.1;
this.forceUpdate(); //not needed, if setTimeout is gone...
}, 1000);
}
@@ -333,31 +326,28 @@ export class Keyframe extends React.Component<IProps> {
*/
@action
makeKeyframeMenu = (kf: Doc, e: MouseEvent) => {
- TimelineMenu.Instance.addItem("button", "Show Data", () => {
- runInAction(() => {
+ TimelineMenu.Instance.addItem("button", "Toggle Fade Only", () => {
+ kf.type = kf.type === KeyframeFunc.KeyframeType.fade ? KeyframeFunc.KeyframeType.default : KeyframeFunc.KeyframeType.fade;
+ }),
+ TimelineMenu.Instance.addItem("button", "Show Data", action(() => {
const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
CollectionDockingView.AddRightSplit(kvp, emptyPath);
- });
- }),
- TimelineMenu.Instance.addItem("button", "Delete", () => {
- runInAction(() => {
- (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
- this.forceUpdate();
- });
- }),
- TimelineMenu.Instance.addItem("input", "Move", (val) => {
- runInAction(() => {
- let cannotMove: boolean = false;
- const kfIndex: number = this.keyframes.indexOf(kf);
- if (val < 0 || (val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time))) {
- cannotMove = true;
- }
- if (!cannotMove) {
- this.keyframes[kfIndex].time = parseInt(val, 10);
- this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn;
- }
- });
- });
+ })),
+ TimelineMenu.Instance.addItem("button", "Delete", action(() => {
+ (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
+ this.forceUpdate();
+ })),
+ TimelineMenu.Instance.addItem("input", "Move", action((val) => {
+ let cannotMove: boolean = false;
+ const kfIndex: number = this.keyframes.indexOf(kf);
+ if (val < 0 || (val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time))) {
+ cannotMove = true;
+ }
+ if (!cannotMove) {
+ this.keyframes[kfIndex].time = parseInt(val, 10);
+ this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn;
+ }
+ }));
TimelineMenu.Instance.addMenu("Keyframe");
TimelineMenu.Instance.openMenu(e.clientX, e.clientY);
}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 30692944d..43f15a33f 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -1,19 +1,17 @@
-import * as React from "react";
-import "./Timeline.scss";
-import { listSpec } from "../../../fields/Schema";
+import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
-import { Track } from "./Track";
-import { observable, action, computed, runInAction, IReactionDisposer, reaction, trace } from "mobx";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types";
-import { List } from "../../../fields/List";
+import * as React from "react";
import { Doc, DocListCast } from "../../../fields/Doc";
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faPlayCircle, faBackward, faForward, faGripLines, faPauseCircle, faEyeSlash, faEye, faCheckCircle, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
-import { ContextMenu } from "../ContextMenu";
-import { TimelineOverview } from "./TimelineOverview";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
import { FieldViewProps } from "../nodes/FieldView";
import { KeyframeFunc } from "./Keyframe";
-import { Utils } from "../../../Utils";
+import "./Timeline.scss";
+import { TimelineOverview } from "./TimelineOverview";
+import { Track } from "./Track";
+import clamp from "../../util/clamp";
/**
* Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are
@@ -39,7 +37,6 @@ import { Utils } from "../../../Utils";
@observer
export class Timeline extends React.Component<FieldViewProps> {
-
//readonly constants
private readonly DEFAULT_TICK_SPACING: number = 50;
private readonly MAX_TITLE_HEIGHT = 75;
@@ -57,7 +54,7 @@ export class Timeline extends React.Component<FieldViewProps> {
@observable private _infoContainer = React.createRef<HTMLDivElement>();
@observable private _roundToggleRef = React.createRef<HTMLDivElement>();
@observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
- @observable private _timeInputRef = React.createRef<HTMLInputElement>();
+
//boolean vars and instance vars
@observable private _currentBarX: number = 0;
@@ -71,27 +68,18 @@ export class Timeline extends React.Component<FieldViewProps> {
@observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
@observable private _time = 100000; //DEFAULT
@observable private _playButton = faPlayCircle;
- @observable private _mouseToggled = false;
- @observable private _doubleClickEnabled = false;
@observable private _titleHeight = 0;
- // so a reaction can be made
- @observable public _isAuthoring = this.props.Document.isATOn;
-
/**
* collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
*/
@computed
- private get children(): List<Doc> {
- const extendedDocument = ["image", "video", "pdf"].includes(StrCast(this.props.Document.type));
- if (extendedDocument) {
- if (this.props.Document.data_ext) {
- return Cast((Cast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"], Doc) as Doc).annotations, listSpec(Doc)) as List<Doc>;
- } else {
- return new List<Doc>();
- }
+ private get children(): Doc[] {
+ const annotatedDoc = ["image", "video", "pdf"].includes(StrCast(this.props.Document.type));
+ if (annotatedDoc) {
+ return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"]);
}
- return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)) as List<Doc>;
+ return DocListCast(this.props.Document[this.props.fieldKey]);
}
/////////lifecycle functions////////////
@@ -144,9 +132,7 @@ export class Timeline extends React.Component<FieldViewProps> {
}
//for playing
- @action
onPlay = (e: React.MouseEvent) => {
- e.preventDefault();
e.stopPropagation();
this.play();
}
@@ -156,24 +142,15 @@ export class Timeline extends React.Component<FieldViewProps> {
*/
@action
play = () => {
- if (this._isPlaying) {
- this._isPlaying = false;
- this._playButton = faPlayCircle;
- } else {
- this._isPlaying = true;
- this._playButton = faPauseCircle;
- const playTimeline = () => {
- if (this._isPlaying) {
- if (this._currentBarX >= this._totalLength) {
- this.changeCurrentBarX(0);
- } else {
- this.changeCurrentBarX(this._currentBarX + this._windSpeed);
- }
- setTimeout(playTimeline, 15);
- }
- };
- playTimeline();
- }
+ const playTimeline = () => {
+ if (this._isPlaying) {
+ this.changeCurrentBarX(this._currentBarX >= this._totalLength ? 0 : this._currentBarX + this._windSpeed);
+ setTimeout(playTimeline, 15);
+ }
+ };
+ this._isPlaying = !this._isPlaying;
+ this._playButton = this._isPlaying ? faPauseCircle : faPlayCircle;
+ this._isPlaying && playTimeline();
}
@@ -206,12 +183,7 @@ export class Timeline extends React.Component<FieldViewProps> {
*/
@action
onScrubberDown = (e: React.PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- document.addEventListener("pointermove", this.onScrubberMove);
- document.addEventListener("pointerup", () => {
- document.removeEventListener("pointermove", this.onScrubberMove);
- });
+ setupMoveUpEvents(this, e, this.onScrubberMove, emptyFunction, emptyFunction);
}
/**
@@ -219,12 +191,11 @@ export class Timeline extends React.Component<FieldViewProps> {
*/
@action
onScrubberMove = (e: PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
const scrubberbox = this._infoContainer.current!;
const left = scrubberbox.getBoundingClientRect().left;
const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale;
this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
+ return false;
}
/**
@@ -232,27 +203,8 @@ export class Timeline extends React.Component<FieldViewProps> {
*/
@action
onPanDown = (e: React.PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- const clientX = e.clientX;
- if (this._doubleClickEnabled) {
- this._doubleClickEnabled = false;
- } else {
- setTimeout(() => {
- if (!this._mouseToggled && this._doubleClickEnabled) this.changeCurrentBarX(this._trackbox.current!.scrollLeft + clientX - this._trackbox.current!.getBoundingClientRect().left);
- this._mouseToggled = false;
- this._doubleClickEnabled = false;
- }, 200);
- this._doubleClickEnabled = true;
- document.addEventListener("pointermove", this.onPanMove);
- document.addEventListener("pointerup", () => {
- document.removeEventListener("pointermove", this.onPanMove);
- if (!this._doubleClickEnabled) {
- this._mouseToggled = false;
- }
- });
-
- }
+ setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, (e) =>
+ this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left));
}
/**
@@ -260,11 +212,6 @@ export class Timeline extends React.Component<FieldViewProps> {
*/
@action
onPanMove = (e: PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- if (e.movementX !== 0 || e.movementY !== 0) {
- this._mouseToggled = true;
- }
const trackbox = this._trackbox.current!;
const titleContainer = this._titleContainer.current!;
this.movePanX(this._visibleStart - e.movementX);
@@ -276,43 +223,25 @@ export class Timeline extends React.Component<FieldViewProps> {
this._time -= KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this._tickSpacing, this._tickIncrement);
this.props.Document.AnimationLength = this._time;
}
-
+ return false;
}
@action
movePanX = (pixel: number) => {
- const infoContainer = this._infoContainer.current!;
- infoContainer.scrollLeft = pixel;
- this._visibleStart = infoContainer.scrollLeft;
+ this._infoContainer.current!.scrollLeft = pixel;
+ this._visibleStart = this._infoContainer.current!.scrollLeft;
}
/**
* resizing timeline (in editing mode) (the hamburger drag icon)
*/
- @action
onResizeDown = (e: React.PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- document.addEventListener("pointermove", this.onResizeMove);
- document.addEventListener("pointerup", () => {
- document.removeEventListener("pointermove", this.onResizeMove);
- });
- }
-
- @action
- onResizeMove = (e: PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
- // let offset = 0;
- if (this._containerHeight + offset <= this.MIN_CONTAINER_HEIGHT) {
- this._containerHeight = this.MIN_CONTAINER_HEIGHT;
- } else if (this._containerHeight + offset >= this.MAX_CONTAINER_HEIGHT) {
- this._containerHeight = this.MAX_CONTAINER_HEIGHT;
- } else {
- this._containerHeight += offset;
- }
+ setupMoveUpEvents(this, e, action((e) => {
+ const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
+ this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT);
+ return false;
+ }), emptyFunction, emptyFunction);
}
/**
@@ -323,8 +252,8 @@ export class Timeline extends React.Component<FieldViewProps> {
time = time / 1000;
const inSeconds = Math.round(time * 100) / 100;
- const min: (string | number) = Math.floor(inSeconds / 60);
- const sec: (string | number) = (Math.round((inSeconds % 60) * 100) / 100);
+ const min = Math.floor(inSeconds / 60);
+ const sec = (Math.round((inSeconds % 60) * 100) / 100);
let secString = sec.toFixed(2);
if (Math.floor(sec / 10) === 0) {
@@ -346,7 +275,7 @@ export class Timeline extends React.Component<FieldViewProps> {
const offset = e.clientX - this._infoContainer.current!.getBoundingClientRect().left;
const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, "mili", "time", this._tickSpacing, this._tickIncrement);
const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement);
- e.deltaY < 0 ? this.zoom(true) : this.zoom(false);
+ this.zoom(e.deltaY < 0);
const currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement);
const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement);
this._infoContainer.current!.scrollLeft = currPixel - offset;
@@ -405,30 +334,17 @@ export class Timeline extends React.Component<FieldViewProps> {
private timelineToolBox = (scale: number, totalTime: number) => {
const size = 40 * scale; //50 is default
const iconSize = 25;
+ const width: number = this.props.PanelWidth();
+ const modeType = this.props.Document.isATOn ? "Author" : "Play";
//decides if information should be omitted because the timeline is very small
// if its less than 950 pixels then it's going to be overlapping
- let shouldCompress = false;
- const width: number = this.props.PanelWidth();
+ let modeString = modeType, overviewString = "", lengthString = "";
if (width < 850) {
- shouldCompress = true;
- }
-
- let modeString, overviewString, lengthString;
- const modeType = this.props.Document.isATOn ? "Author" : "Play";
-
- if (!shouldCompress) {
modeString = "Mode: " + modeType;
overviewString = "Overview:";
lengthString = "Length: ";
}
- else {
- modeString = modeType;
- overviewString = "";
- lengthString = "";
- }
-
- // let rightInfo = this.timeIndicator;
return (
<div key="timeline_toolbox" className="timeline-toolbox" style={{ height: `${size}px` }}>
@@ -451,8 +367,7 @@ export class Timeline extends React.Component<FieldViewProps> {
<div className="time-box overview-tool" style={{ display: "flex" }}>
{this.timeIndicator(lengthString, totalTime)}
<div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}><FontAwesomeIcon icon="compress-arrows-alt" size="lg" /></div>
- <div className="resetView-tool" style={{ display: this._isAuthoring ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div>
-
+ <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div>
</div>
</div>
</div>
@@ -498,15 +413,15 @@ export class Timeline extends React.Component<FieldViewProps> {
const roundToggle = this._roundToggleRef.current!;
const roundToggleContainer = this._roundToggleContainerRef.current!;
const timelineContainer = this._timelineContainer.current!;
- if (BoolCast(this.props.Document.isATOn)) {
+
+ this.props.Document.isATOn = !this.props.Document.isATOn;
+ if (!BoolCast(this.props.Document.isATOn)) {
//turning on playmode...
roundToggle.style.transform = "translate(0px, 0px)";
roundToggle.style.animationName = "turnoff";
roundToggleContainer.style.animationName = "turnoff";
roundToggleContainer.style.backgroundColor = "white";
timelineContainer.style.top = `${-this._containerHeight}px`;
- this.props.Document.isATOn = false;
- this._isAuthoring = false;
this.toPlay();
} else {
//turning on authoring mode...
@@ -515,8 +430,6 @@ export class Timeline extends React.Component<FieldViewProps> {
roundToggleContainer.style.animationName = "turnon";
roundToggleContainer.style.backgroundColor = "#9acedf";
timelineContainer.style.top = "0px";
- this.props.Document.isATOn = true;
- this._isAuthoring = true;
this.toAuthoring();
}
}
@@ -533,10 +446,7 @@ export class Timeline extends React.Component<FieldViewProps> {
// @computed
getCurrentTime = () => {
let current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement);
- if (current > this._time) {
- current = this._time;
- }
- return this.toReadTime(current);
+ return this.toReadTime(current > this._time ? this._time : current);
}
@observable private mapOfTracks: (Track | null)[] = [];
@@ -562,18 +472,13 @@ export class Timeline extends React.Component<FieldViewProps> {
@action
toAuthoring = () => {
- let longestTime = this.findLongestTime();
- if (longestTime === 0) longestTime = 1;
- const adjustedTime = Math.ceil(longestTime / 100000) * 100000;
- // console.log(adjustedTime);
- this._totalLength = KeyframeFunc.convertPixelTime(adjustedTime, "mili", "pixel", this._tickSpacing, this._tickIncrement);
- this._time = adjustedTime;
+ this._time = Math.ceil((this.findLongestTime() ?? 1) / 100000) * 100000;
+ this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement);
}
@action
toPlay = () => {
- const longestTime = this.findLongestTime();
- this._time = longestTime;
+ this._time = this.findLongestTime();
this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement);
}
@@ -582,40 +487,35 @@ export class Timeline extends React.Component<FieldViewProps> {
* basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region)
*/
render() {
- setTimeout(() => {
- this.changeLengths();
- // this.toPlay();
- // this._time = longestTime;
- }, 0);
+ setTimeout(() => this.changeLengths(), 0);
- const longestTime = this.findLongestTime();
trace();
// change visible and total width
return (
<div style={{ visibility: "visible" }}>
- <div key="timeline_wrapper" style={{ visibility: BoolCast(this.props.Document.isATOn) ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
+ <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
<div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
- <div key="timeline_info" className="info-container" ref={this._infoContainer} onWheel={this.onWheelZoom}>
+ <div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}>
{this.drawTicks()}
<div key="timeline_scrubber" className="scrubber" style={{ transform: `translate(${this._currentBarX}px)` }}>
<div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} ></div>
</div>
- <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} onPointerDown={this.onPanDown} style={{ width: `${this._totalLength}px` }}>
- {DocListCast(this.children).map(doc =>
+ <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
+ {this.children.map(doc =>
<Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} />
)}
</div>
</div>
<div className="currentTime">Current: {this.getCurrentTime()}</div>
<div key="timeline_title" className="title-container" ref={this._titleContainer}>
- {DocListCast(this.children).map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)}
+ {this.children.map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)}
</div>
<div key="timeline_resize" onPointerDown={this.onResizeDown}>
<FontAwesomeIcon className="resize" icon={faGripLines} />
</div>
</div>
</div>
- {this.timelineToolBox(1, longestTime)}
+ {this.timelineToolBox(1, this.findLongestTime())}
</div>
);
}
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index fc96c320a..4987006e7 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -31,13 +31,14 @@ export class Track extends React.Component<IProps> {
@observable private _autoKfReaction: any;
@observable private _newKeyframe: boolean = false;
private readonly MAX_TITLE_HEIGHT = 75;
- private _trackHeight = 0;
+ @observable private _trackHeight = 0;
private primitiveWhitelist = [
"x",
"y",
"_width",
"_height",
"opacity",
+ "_scrollTop"
];
private objectWhitelist = [
"data"
@@ -51,7 +52,7 @@ export class Track extends React.Component<IProps> {
if (!regions) this.props.node.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
//these two lines are exactly same from timeline.tsx
const relativeHeight = window.innerHeight / 20;
- this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //for responsiveness
+ runInAction(() => this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT); //for responsiveness
this._timelineVisibleReaction = this.timelineVisibleReaction();
this._currentBarXReaction = this.currentBarXReaction();
if (DocListCast(this.props.node.regions).length === 0) this.createRegion(this.time);
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index a0b7cd8a8..cfec3a6bc 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -42,7 +42,7 @@ const query = async (data: string | google.maps.LatLngLiteral) => {
};
@observer
-class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps> & { google: any }>(MapSchema) {
+export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps> & { google: any }>(MapSchema) {
private _cancelAddrReq = new Map<string, boolean>();
private _cancelLocReq = new Map<string, boolean>();
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 020a87b2e..fc48e0327 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -23,7 +23,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
@observable _childClickedScript: Opt<ScriptField>;
componentDidMount() {
if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
- this.Document._layoutEngine = "pass";
+ this.Document._pileLayoutEngine = "pass";
}
this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
this.layoutDoc._chromeStatus = "disabled";
@@ -34,7 +34,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
this.layoutDoc._chromeStatus = this._originalChrome;
}
- layoutEngine = () => StrCast(this.Document._layoutEngine);
+ layoutEngine = () => StrCast(this.Document._pileLayoutEngine);
@computed get contents() {
return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
@@ -53,7 +53,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
Doc.pileup(this.childDocs);
this.layoutDoc._panX = 0;
this.layoutDoc._panY = -10;
- this.props.Document._layoutEngine = 'pass';
+ this.props.Document._pileLayoutEngine = 'pass';
} else {
const defaultSize = 25;
this.layoutDoc._overflow = 'visible';
@@ -67,7 +67,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}
this.layoutDoc._panX = this.layoutDoc._panY = 0;
this.layoutDoc._width = this.layoutDoc._height = defaultSize;
- this.props.Document._layoutEngine = 'starburst';
+ this.props.Document._pileLayoutEngine = 'starburst';
}
});
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 5eaf29316..203c51163 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -7,11 +7,13 @@
.collectionStackingView {
display: flex;
}
+
.collectionStackingMasonry-cont {
- position:relative;
- height:100%;
- width:100%;
+ position: relative;
+ height: 100%;
+ width: 100%;
}
+
.collectionStackingView,
.collectionMasonryView {
height: 100%;
@@ -22,12 +24,17 @@
overflow-x: hidden;
flex-wrap: wrap;
transition: top .5s;
+
>div {
position: relative;
display: block;
}
+
.collectionStackingViewFieldColumn {
- height:max-content;
+ height: max-content;
+ }
+ .collectionStackingViewFieldColumnDragging {
+ height:100%;
}
.collectionSchemaView-previewDoc {
@@ -129,27 +136,34 @@
background: red;
}
}
+
.collectionStackingView-miniHeader {
width: 100%;
+
.editableView-container-editing-oneLine {
min-height: 20px;
display: flex;
align-items: center;
flex-direction: row;
}
- span::before , span::after{
+
+ span::before,
+ span::after {
content: "";
width: 50%;
border-top: dashed gray 1px;
position: relative;
display: inline-block;
}
+
span::before {
margin-right: 10px;
}
- span::after{
+
+ span::after {
margin-left: 10px;
}
+
span {
position: relative;
text-align: center;
@@ -157,10 +171,11 @@
overflow: visible;
width: 100%;
display: flex;
- color:gray;
+ color: gray;
align-items: center;
}
}
+
.collectionStackingView-sectionHeader {
text-align: center;
margin: auto;
@@ -277,7 +292,7 @@
height: 20px;
border-radius: 10px;
margin: 3px;
- width:max-content;
+ width: max-content;
&.active {
color: red;
@@ -294,15 +309,18 @@
display: none;
}
}
+
.collectionStackingView-sectionHeader:hover {
.collectionStackingView-sectionColor {
- display:unset;
+ display: unset;
}
+
.collectionStackingView-sectionOptions {
- display:unset;
+ display: unset;
}
+
.collectionStackingView-sectionDelete {
- display:unset;
+ display: unset;
}
}
@@ -403,4 +421,4 @@
.rc-switch-checked .rc-switch-inner {
left: 8px;
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6949670d6..bd48d1727 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -193,7 +193,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
const height = () => this.getDocHeight(doc);
- const opacity = () => this.Document.currentTimecode === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, this.Document.currentTimecode || 0)?.opacity;
+ const opacity = () => this.Document.currentFrame === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document.currentFrame))?.opacity;
return <ContentFittingDocumentView
Document={doc}
DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
@@ -474,7 +474,10 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
width: `${1 / this.scaling * 100}%`,
transformOrigin: "top left",
}}
- onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
+ onScroll={action(e => {
+ if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+ else this._scroll = e.currentTarget.scrollTop;
+ })}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this.props.active() && e.stopPropagation()} >
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 53435ccc9..a269b21f5 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -352,7 +352,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
const chromeStatus = this.props.parent.props.Document._chromeStatus;
return (
- <div className="collectionStackingViewFieldColumn" key={heading}
+ <div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
style={{
width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 423eb1d90..167bce131 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,27 +6,19 @@ import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
+import { WebField } from "../../../fields/URLField";
import { Cast, ScriptCast, NumCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { Upload } from "../../../server/SharedMediaTypes";
import { Utils } from "../../../Utils";
-import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
import { Networking } from "../../Network";
-import { DragManager, dropActionType } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { InteractionUtils } from "../../util/InteractionUtils";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
-import { CollectionView } from "./CollectionView";
import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
-import { WebField } from "../../../fields/URLField";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -343,7 +335,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (focusNode) {
const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
const x = (rect?.x || 0);
- const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0);
+ const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
anchor.context = srcWeb;
const key = Doc.LayoutFieldKey(srcWeb);
@@ -405,7 +397,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
const type = "html";// (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
if (type) {
- const doc = await Docs.Get.DocumentFromType(type, stringContents, options);
+ const doc = await DocUtils.DocumentFromType(type, stringContents, options);
doc && generatedDocuments.push(doc);
}
}
@@ -435,7 +427,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
const full = { ...options, _width: 400, title: name };
const pathname = Utils.prepend(result.accessPaths.agnostic.client);
- const doc = await Docs.Get.DocumentFromType(type, pathname, full);
+ const doc = await DocUtils.DocumentFromType(type, pathname, full);
if (!doc) {
continue;
}
@@ -450,9 +442,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
generatedDocuments.push(doc);
}
if (generatedDocuments.length) {
- const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
+ const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d));
if (set) {
- addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!)!);
+ addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
} else {
generatedDocuments.forEach(addDocument);
}
@@ -469,3 +461,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
return CollectionSubView;
}
+import { DragManager, dropActionType } from "../../util/DragManager";
+import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
+import { CollectionView } from "./CollectionView";
+import { SelectionManager } from "../../util/SelectionManager";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3e99af724..e891c4274 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -717,7 +717,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
ContextMenu.Instance.addItem({
description: "Buxton Layout", icon: "eye", event: () => {
- const { ImageDocument } = Docs.Create;
+ const { ImageDocument, PdfDocument } = Docs.Create;
const { Document } = this.props;
const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
const detailView = Cast(Cast(Doc.UserDoc()["template-button-detail"], Doc, null)?.dragFactory, Doc, null);
@@ -726,13 +726,14 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
heroView._showTitle = "title";
heroView._showTitleHover = "titlehover";
- const doubleClickView = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 }); // replace with desired double click target
+ const fallback = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 }); // replace with desired double click target
+ let pdfContent: string;
DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
DocListCast(d.data).map((img, i) => {
const caption = (d.captions as any)[i];
if (caption) {
Doc.GetProto(img).caption = caption;
- Doc.GetProto(img).doubleClickView = doubleClickView;
+ Doc.GetProto(img).doubleClickView = (pdfContent = StrCast(img.additionalMedia_pdfs)) ? PdfDocument(pdfContent, { title: pdfContent }) : fallback;
}
});
Doc.GetProto(d).type = "buxton";
@@ -748,7 +749,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
Document.childLayoutTemplate = heroView;
Document.childClickedOpenTemplateView = new PrefetchProxy(detailView);
Document._viewType = CollectionViewType.Time;
- Document._forceActive = true;
+ Document.forceActive = true;
Document._pivotField = "company";
Document.childDropAction = "alias";
}
@@ -756,7 +757,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({
- description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
+ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
});
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ee4755355..3330abbc4 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,21 +1,28 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye, faEdit } from '@fortawesome/free-regular-svg-icons';
+import { faEdit, faEye } from '@fortawesome/free-regular-svg-icons';
+import { faColumns, faCopy, faEllipsisV, faFingerprint, faGlobeAmericas, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree, faGlobeAmericas } from '@fortawesome/free-solid-svg-icons';
-import { action, observable, computed } from 'mobx';
+import { action, computed, observable } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../fields/Types';
+import { ObjectField } from '../../../fields/ObjectField';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
+import { InteractionUtils } from '../../util/InteractionUtils';
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { ScriptBox } from '../ScriptBox';
@@ -25,8 +32,10 @@ import { CollectionDockingView } from "./CollectionDockingView";
import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionMapView } from './CollectionMapView';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
+import { CollectionPileView } from './CollectionPileView';
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
import { CollectionStaffView } from './CollectionStaffView';
@@ -35,20 +44,12 @@ import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { Id } from '../../../fields/FieldSymbols';
-import { listSpec } from '../../../fields/Schema';
-import { Docs } from '../../documents/Documents';
-import { ScriptField, ComputedField } from '../../../fields/ScriptField';
-import { InteractionUtils } from '../../util/InteractionUtils';
-import { ObjectField } from '../../../fields/ObjectField';
-import CollectionMapView from './CollectionMapView';
-import { CollectionPileView } from './CollectionPileView';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
+
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faGlobeAmericas, faEllipsisV, faImage, faEye as any, faCopy);
export enum CollectionViewType {
@@ -126,10 +127,16 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
const added = docs.filter(d => !docList.includes(d));
if (added.length) {
- added.map(doc => doc.context = this.props.Document);
- added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
- targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ if (this.dataDoc[AclSym] === AclReadonly) {
+ return false;
+ } else if (this.dataDoc[AclSym] === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
+ } else {
+ added.map(doc => doc.context = this.props.Document);
+ added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
+ targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
return true;
}
@@ -513,4 +520,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
{this.facetWidth() < 10 ? (null) : this.filterView}
</div>);
}
-} \ No newline at end of file
+}
+
+
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index e4581eb46..03bd9a01a 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -8,10 +8,12 @@
z-index: 9001;
transition: top .5s;
background: lightgrey;
+ transform-origin: top left;
.collectionViewChrome {
display: flex;
- padding-bottom: 10px;
+ padding-bottom: 1px;
+ height:32px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: hidden;
@@ -21,12 +23,12 @@
.collectionViewBaseChrome-viewPicker {
font-size: 75%;
//text-transform: uppercase;
- letter-spacing: 2px;
+ //letter-spacing: 2px;
background: rgb(238, 238, 238);
color: grey;
outline-color: black;
border: none;
- padding: 12px 10px 11px 10px;
+ //padding: 12px 10px 11px 10px;
}
.collectionViewBaseChrome-viewPicker:active {
@@ -47,6 +49,7 @@
.collectionViewBaseChrome-cmdPicker {
margin-left: 3px;
margin-right: 0px;
+ font-size: 75%;
background: rgb(238, 238, 238);
border: none;
color: grey;
@@ -56,6 +59,7 @@
background-color: gray;
display: flex;
flex-direction: row;
+ height:30px;
.commandEntry-drop {
color:white;
width:25px;
@@ -95,8 +99,8 @@
margin: auto;
background: gray;
color: white;
- width: 40px;
- height: 40px;
+ width: 30px;
+ height: 30px;
align-items: center;
justify-content: center;
}
@@ -195,8 +199,7 @@
.collectionTreeViewChrome-pivotField-label {
vertical-align: center;
padding-left: 10px;
- padding-top: 10px;
- padding-bottom: 10px;
+ margin:auto;
}
.collectionStackingViewChrome-pivotField,
@@ -204,14 +207,15 @@
color: white;
width:100%;
min-width: 100px;
- text-align: center;
+ display: flex;
+ align-items: center;
background: rgb(238, 238, 238);
.editable-view-input,
input,
.editableView-container-editing-oneLine,
.editableView-container-editing {
- padding: 12px 10px 11px 10px;
+ margin:auto;
border: 0px;
color: grey;
text-align: center;
@@ -235,6 +239,44 @@
}
}
+.collectionFreeFormViewChrome-cont {
+ width: 60px;
+ display: flex;
+ position: relative;
+ align-items: center;
+ .fwdKeyframe, .numKeyframe, .backKeyframe {
+ cursor: pointer;
+ position: absolute;
+ width: 20;
+ height: 30;
+ bottom: 0;
+ background: gray;
+ display: flex;
+ align-items: center;
+ color:white;
+ }
+ .backKeyframe {
+ left:0;
+ svg {
+ display:block;
+ margin:auto;
+ }
+ }
+ .numKeyframe {
+ left:20;
+ display: flex;
+ flex-direction: column;
+ padding: 5px;
+ }
+ .fwdKeyframe {
+ left:40;
+ svg {
+ display:block;
+ margin:auto;
+ }
+ }
+}
+
.collectionSchemaViewChrome-cont {
display: flex;
font-size: 10.5px;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 5dc0b09ac..29a3e559a 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -15,6 +15,7 @@ import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
import { CollectionViewType } from "./CollectionView";
import { CollectionView } from "./CollectionView";
import "./CollectionViewChromes.scss";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
@@ -44,7 +45,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
initialize: emptyFunction,
};
_narrativeCommand = {
- params: ["target", "source"], title: "=> click clicked open view",
+ params: ["target", "source"], title: "=> child click view",
script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
@@ -83,22 +84,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
private _viewRef = React.createRef<HTMLInputElement>();
@observable private _currentKey: string = "";
- componentDidMount = () => {
- runInAction(() => {
- // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
- const chromeStatus = this.props.CollectionView.props.Document._chromeStatus;
- if (chromeStatus) {
- if (chromeStatus === "disabled") {
- throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
- }
- else if (chromeStatus === "collapsed") {
- if (this.props.collapse) {
- this.props.collapse(true);
- }
- }
- }
- });
- }
+ componentDidMount = action(() => {
+ // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+ switch (this.props.CollectionView.props.Document._chromeStatus) {
+ case "disabled":
+ throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
+ case "collapsed":
+ this.props.collapse?.(true);
+ break;
+ }
+ })
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
@@ -197,10 +192,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
}
- subChrome = () => {
+ @computed get subChrome() {
const collapsed = this.document._chromeStatus !== "enabled";
if (collapsed) return null;
switch (this.props.type) {
+ case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
@@ -236,7 +232,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
const vtype = this.props.CollectionView.collectionViewType;
const c = {
params: ["target"], title: vtype,
- script: `this.target._viewType = ${StrCast(this.props.CollectionView.props.Document._viewType)}`,
+ script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
@@ -254,14 +250,61 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}, emptyFunction, emptyFunction);
}
+ @computed get templateChrome() {
+ const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+ return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
+ <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <div className="commandEntry-drop">
+ <FontAwesomeIcon icon="bullseye" size="2x" />
+ </div>
+ <select
+ className="collectionViewBaseChrome-cmdPicker"
+ onPointerDown={stopPropagation}
+ onChange={this.commandChanged}
+ value={this._currentKey}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
+ {this._buttonizableCommands.map(cmd =>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+ )}
+ </select>
+ </div>
+ </div>;
+ }
+
+ @computed get viewModes() {
+ const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+ return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+ <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
+ <div className="commandEntry-drop">
+ <FontAwesomeIcon icon="bullseye" size="2x" />
+ </div>
+ <select
+ className="collectionViewBaseChrome-viewPicker"
+ onPointerDown={stopPropagation}
+ onChange={this.viewChanged}
+ value={StrCast(this.props.CollectionView.props.Document._viewType)}>
+ {Object.values(CollectionViewType).map(type => ["invalid", "docking"].includes(type) ? (null) : (
+ <option
+ key={Utils.GenerateGuid()}
+ className="collectionViewBaseChrome-viewOption"
+ onPointerDown={stopPropagation}
+ value={type}>
+ {type[0].toUpperCase() + type.substring(1)}
+ </option>
+ ))}
+ </select>
+ </div>
+ </div>;
+ }
+
render() {
const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+ const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale);
return (
<div className="collectionViewChrome-cont" style={{
top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
- transform: collapsed ? "" : `scale(${Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale)})`,
- transformOrigin: "top left",
- width: `${this.props.PanelWidth() / Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale)}px`
+ transform: collapsed ? "" : `scale(${scale})`,
+ width: `${this.props.PanelWidth() / scale}px`
}}>
<div className="collectionViewChrome" style={{ border: "unset", pointerEvents: collapsed ? "none" : undefined }}>
<div className="collectionViewBaseChrome">
@@ -276,52 +319,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
title="Collapse collection chrome" onClick={this.toggleCollapse}>
<FontAwesomeIcon icon="caret-up" size="2x" />
</button>
- <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
- <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
- <div className="commandEntry-drop">
- <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
- </div>
- <select
- className="collectionViewBaseChrome-viewPicker"
- onPointerDown={stopPropagation}
- onChange={this.viewChanged}
- value={StrCast(this.props.CollectionView.props.Document._viewType)}>
- {Object.values(CollectionViewType).map(type => ["invalid", "docking"].includes(type) ? (null) : (
- <option
- key={Utils.GenerateGuid()}
- className="collectionViewBaseChrome-viewOption"
- onPointerDown={stopPropagation}
- value={type}>
- {type[0].toUpperCase() + type.substring(1)}
- </option>
- ))}
- </select>
- </div>
- </div>
+ {this.viewModes}
<div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
<div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
<FontAwesomeIcon icon="filter" size="2x" />
</div>
</div>
- <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
- <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <div className="commandEntry-drop">
- <FontAwesomeIcon icon="bullseye" size="2x" />
- </div>
- <select
- className="collectionViewBaseChrome-cmdPicker"
- onPointerDown={stopPropagation}
- onChange={this.commandChanged}
- value={this._currentKey}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""}>{""}</option>
- {this._buttonizableCommands.map(cmd =>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
- )}
- </select>
- </div>
- </div>
+ {this.templateChrome}
</div>
- {this.subChrome()}
+ {this.subChrome}
</div>
</div>
);
@@ -329,6 +335,56 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@observer
+export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
+
+ get Document() { return this.props.CollectionView.props.Document; }
+ @computed get dataField() {
+ return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+ }
+ @computed get childDocs() {
+ return DocListCast(this.dataField);
+ }
+ @undoBatch
+ @action
+ nextKeyframe = (): void => {
+ const currentFrame = NumCast(this.Document.currentFrame);
+ if (currentFrame === undefined) {
+ this.Document.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+ this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+ }
+ @undoBatch
+ @action
+ prevKeyframe = (): void => {
+ const currentFrame = NumCast(this.Document.currentFrame);
+ if (currentFrame === undefined) {
+ this.Document.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+ this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ }
+ render() {
+ return this.Document.isAnnotationOverlay ? (null) :
+ <div className="collectionFreeFormViewChrome-cont">
+ <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
+ onClick={action(() => this.Document.editing = !this.Document.editing)} >
+ {NumCast(this.Document.currentFrame)}
+ </div>
+ <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+ </div>;
+ }
+}
+
+@observer
export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
@@ -371,6 +427,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
this.suggestions = [];
}
+ @action
setValue = (value: string) => {
this.props.CollectionView.props.Document._pivotField = value;
return true;
@@ -473,19 +530,20 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
@observer
export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
- get dataExtension() {
- return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "_ext"] as Doc;
+ get sortAscending() {
+ return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
}
- @computed private get descending() {
- return this.dataExtension && Cast(this.dataExtension.sortAscending, "boolean", null);
+ set sortAscending(value) {
+ this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+ }
+ @computed private get ascending() {
+ return Cast(this.sortAscending, "boolean", null);
}
@action toggleSort = () => {
- if (this.dataExtension) {
- if (this.dataExtension.sortAscending) this.dataExtension.sortAscending = undefined;
- else if (this.dataExtension.sortAscending === undefined) this.dataExtension.sortAscending = false;
- else this.dataExtension.sortAscending = true;
- }
+ if (this.sortAscending) this.sortAscending = undefined;
+ else if (this.sortAscending === undefined) this.sortAscending = false;
+ else this.sortAscending = true;
}
render() {
@@ -495,7 +553,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
<div className="collectionTreeViewChrome-sortLabel">
Sort
</div>
- <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.descending === undefined ? "90" : this.descending ? "180" : "0"}deg)` }}>
+ <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? "90" : this.ascending ? "180" : "0"}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
</button>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 7a84fcde1..d9011c9d3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -41,23 +41,6 @@
// touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
touch-action: none;
- .fwdKeyframe, .numKeyframe, .backKeyframe{
- cursor: pointer;
- position: absolute;
- width: 20;
- height:20;
- bottom:0;
- background:gray;
- }
- .backKeyframe {
- right:45;
- }
- .numKeyframe {
- right:25;
- }
- .fwdKeyframe {
- right:5;
- }
.collectionfreeformview-placeholder {
background: gray;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b218bc18..379156179 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed, trace } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
@@ -29,7 +29,6 @@ import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { ContextMenu } from "../../ContextMenu";
import { ContextMenuProps } from "../../ContextMenuItem";
-import { InkingControl } from "../../InkingControl";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
@@ -46,7 +45,7 @@ import React = require("react");
import { CollectionViewType } from "../CollectionView";
import { Timeline } from "../../animationtimeline/Timeline";
import { SnappingManager } from "../../../util/SnappingManager";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -56,6 +55,7 @@ export const panZoomSchema = createSchema({
scale: "number",
currentTimecode: "number",
displayTimecode: "number",
+ currentFrame: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
@@ -75,6 +75,7 @@ const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentS
export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
+ childPointerEvents?: boolean;
};
@observer
@@ -116,55 +117,67 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
- private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections
- private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 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());
+ @computed get cachedCenteringShiftX(): number {
+ const scaling = this.fitToContent ? 1 : this.contentScaling;
+ return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0; // shift so pan position is at center of window for non-overlay collections
+ }
+ @computed get cachedCenteringShiftY(): number {
+ const scaling = this.fitToContent ? 1 : this.contentScaling;
+ return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections
+ }
+ @computed get cachedGetLocalTransform(): Transform {
+ return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+ }
+ @computed get cachedGetContainerTransform(): Transform {
+ return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
+ }
+ @computed get cachedGetTransform(): Transform {
+ return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+ }
+
+ private centeringShiftX = () => this.cachedCenteringShiftX;
+ private centeringShiftY = () => this.cachedCenteringShiftY;
+ private getTransform = () => this.cachedGetTransform.copy();
+ private getLocalTransform = () => this.cachedGetLocalTransform.copy();
+ private getContainerTransform = () => this.cachedGetContainerTransform.copy();
+ private getTransformOverlay = () => this.getContainerTransform().translate(1, 1);
private addLiveTextBox = (newBox: Doc) => {
FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox);
}
- addDocument = (newBox: Doc | Doc[]) => {
- if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
- CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.currentTimecode, this.props.Document);
- }
-
+ addDocument = action((newBox: Doc | Doc[]) => {
+ let retVal = false;
if (newBox instanceof Doc) {
- const added = this.props.addDocument(newBox);
- added && this.bringToFront(newBox);
- added && this.updateCluster(newBox);
- return added;
+ retVal = this.props.addDocument(newBox);
+ retVal && this.bringToFront(newBox);
+ retVal && this.updateCluster(newBox);
} else {
- return this.props.addDocument(newBox);
+ retVal = this.props.addDocument(newBox);
// bcz: deal with clusters
}
- }
-
- @undoBatch
- @action
- nextKeyframe = (): void => {
- const currentTimecode = this.Document.currentTimecode;
- if (currentTimecode === undefined) {
- this.Document.currentTimecode = 0;
- CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
- }
- CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
- this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
- this.Document.lastTimecode = Math.max(NumCast(this.Document.currentTimecode), NumCast(this.Document.lastTimecode));
- }
- @undoBatch
- @action
- prevKeyframe = (): void => {
- const currentTimecode = this.Document.currentTimecode;
- if (currentTimecode === undefined) {
- this.Document.currentTimecode = 0;
- CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+ if (retVal) {
+ const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
+ for (let i = 0; i < newBoxes.length; i++) {
+ const newBox = newBoxes[i];
+ if (newBox.activeFrame !== undefined) {
+ const x = newBox.x;
+ const y = newBox.y;
+ delete newBox["x-indexed"];
+ delete newBox["y-indexed"];
+ delete newBox["opacity-indexed"];
+ delete newBox.x;
+ delete newBox.y;
+ delete newBox.activeFrame;
+ newBox.x = x;
+ newBox.y = y;
+ }
+ }
+ if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame);
+ }
}
- CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
- this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) - 1);
- }
+ return retVal;
+ })
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
@@ -176,79 +189,76 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
}
+ onExternalDrop = (e: React.DragEvent) => {
+ return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
+ }
+
+ @undoBatch
@action
- onExternalDrop = (e: React.DragEvent): Promise<void> => {
- const pt = this.getTransform().transformPoint(e.pageX, e.pageY);
- return super.onExternalDrop(e, { x: pt[0], y: pt[1] });
+ internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
+ if (!super.onInternalDrop(e, de)) return false;
+ const [xpo, ypo] = this.getTransformOverlay().transformPoint(de.x, de.y);
+ const z = NumCast(docDragData.droppedDocuments[0].z);
+ const x = (z ? xpo : xp) - docDragData.offset[0];
+ const y = (z ? ypo : yp) - docDragData.offset[1];
+ const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ zsorted.forEach((doc, index) => doc.zIndex = index + 1);
+ const dropPos = [NumCast(docDragData.droppedDocuments[0].x), NumCast(docDragData.droppedDocuments[0].y)];
+ for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
+ const d = docDragData.droppedDocuments[i];
+ const layoutDoc = Doc.Layout(d);
+ if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
+ CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.opacity);
+ } else {
+ d.x = x + NumCast(d.x) - dropPos[0];
+ d.y = y + NumCast(d.y) - dropPos[1];
+ }
+ const nd = [NumCast(layoutDoc._nativeWidth), NumCast(layoutDoc._nativeHeight)];
+ layoutDoc._width = NumCast(layoutDoc._width, 300);
+ layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
+ d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+ }
+
+ (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
+ return true;
}
@undoBatch
@action
+ internalPdfAnnoDrop(e: Event, annoDragData: DragManager.PdfAnnoDragData, xp: number, yp: number) {
+ const dragDoc = annoDragData.dropDocument;
+ const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)];
+ dragDoc.x = xp - annoDragData.offset[0] + (NumCast(dragDoc.x) - dropPos[0]);
+ dragDoc.y = yp - annoDragData.offset[1] + (NumCast(dragDoc.y) - dropPos[1]);
+ annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
+ this.bringToFront(dragDoc);
+ return true;
+ }
+
+ @undoBatch
+ @action
+ internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
+ if (linkDragData.linkSourceDocument === this.props.Document) return false;
+ const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ this.props.addDocument(source);
+ linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+ e.stopPropagation();
+ return true;
+ }
+
+ @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
// if (this.props.Document.isBackground) return false;
- const xf = this.getTransform();
- const xfo = this.getTransformOverlay();
- const [xp, yp] = xf.transformPoint(de.x, de.y);
- const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
- const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- if (!this.isAnnotationOverlay && de.complete.linkDragData && de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
- const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
- this.props.addDocument(source);
- (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceDocument },
- "doc annotation")); // TODODO this is where in text links get passed
- e.stopPropagation();
+ const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ if (this.isAnnotationOverlay !== true && de.complete.linkDragData)
+ return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
+ if (de.complete.annoDragData?.dropDocument && super.onInternalDrop(e, de))
+ return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
+ if (de.complete.docDragData?.droppedDocuments.length && this.internalDocDrop(e, de, de.complete.docDragData, xp, yp)) {
return true;
- }
- if (super.onInternalDrop(e, de)) {
- if (de.complete.docDragData) {
- if (de.complete.docDragData.droppedDocuments.length) {
- const firstDoc = de.complete.docDragData.droppedDocuments[0];
- const z = NumCast(firstDoc.z);
- const x = (z ? xpo : xp) - de.complete.docDragData.offset[0];
- const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
- const dropX = NumCast(firstDoc.x);
- const dropY = NumCast(firstDoc.y);
- const droppedDocs = de.complete.docDragData.droppedDocuments;
- runInAction(() => {
- zsorted.forEach((doc, index) => doc.zIndex = index + 1);
- for (let i = 0; i < droppedDocs.length; i++) {
- const d = droppedDocs[i];
- const layoutDoc = Doc.Layout(d);
- if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
- const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.displayTimecode, 1000));
- CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity);
- } else {
- d.x = x + NumCast(d.x) - dropX;
- d.y = y + NumCast(d.y) - dropY;
- }
- if (!NumCast(layoutDoc._width)) {
- layoutDoc._width = 300;
- }
- if (!NumCast(layoutDoc._height)) {
- const nw = NumCast(layoutDoc._nativeWidth);
- const nh = NumCast(layoutDoc._nativeHeight);
- layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
- }
- d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
- }
- });
-
- (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
- }
- }
- else if (de.complete.annoDragData) {
- if (de.complete.annoDragData.dropDocument) {
- const dragDoc = de.complete.annoDragData.dropDocument;
- const x = xp - de.complete.annoDragData.offset[0];
- const y = yp - de.complete.annoDragData.offset[1];
- const dropX = NumCast(dragDoc.x);
- const dropY = NumCast(dragDoc.y);
- dragDoc.x = x + NumCast(dragDoc.x) - dropX;
- dragDoc.y = y + NumCast(dragDoc.y) - dropY;
- de.complete.annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
- this.bringToFront(dragDoc);
- }
- }
+ } else {
+ UndoManager.Undo();
}
return false;
}
@@ -379,7 +389,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
return;
}
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
@@ -396,7 +406,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
// if not using a pen and in no ink mode
- if (InkingControl.Instance.selectedTool === InkTool.None) {
+ if (Doc.GetSelectedTool() === InkTool.None) {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
}
@@ -420,13 +430,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.addMoveListeners();
this.removeEndListeners();
this.addEndListeners();
- // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+ // if (Doc.SelectedTool() === InkTool.Highlighter || Doc.SelectedTool() === InkTool.Pen) {
// e.stopPropagation();
// e.preventDefault();
// const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
// this._points.push({ X: point[0], Y: point[1] });
// }
- if (InkingControl.Instance.selectedTool === InkTool.None) {
+ if (Doc.GetSelectedTool() === InkTool.None) {
this._lastX = pt.pageX;
this._lastY = pt.pageY;
e.preventDefault();
@@ -446,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, InkingControl.Instance.selectedWidth, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+ const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -482,9 +492,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
console.log("end");
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
- const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color);
+ const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color);
const sets = setDocs.map((sd) => {
- return Cast(sd.data, RichTextField)?.Text as string;
+ return Cast(sd.text, RichTextField)?.Text as string;
});
if (sets.length && sets[0]) {
this._wordPalette.clear();
@@ -520,6 +530,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
});
+ console.log(this._wordPalette)
CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
console.log(results);
const wordResults = results.filter((r: any) => r.category === "inkWord");
@@ -606,7 +617,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return;
}
if (!e.cancelBubble) {
- const selectedTool = InkingControl.Instance.selectedTool;
+ const selectedTool = Doc.GetSelectedTool();
if (selectedTool === InkTool.None) {
if (this._hitCluster && 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
@@ -628,7 +639,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt = myTouches[0];
if (pt) {
- if (InkingControl.Instance.selectedTool === InkTool.None) {
+ if (Doc.GetSelectedTool() === InkTool.None) {
if (this._hitCluster && 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();
@@ -875,7 +886,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
} else {
const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
const offset = annotOn && (contextHgt / 2 * 96 / 72);
- this.props.Document.scrollY = NumCast(doc.y) - offset;
+ this.props.Document._scrollY = NumCast(doc.y) - offset;
}
afterFocus && setTimeout(afterFocus, 1000);
@@ -987,12 +998,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
}
const layoutDoc = Doc.Layout(params.pair.layout);
- const { x, y, opacity } = this.Document.currentTimecode === undefined ? params.pair.layout :
- CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentTimecode || 0);
+ const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
+ CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
const { z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- transition: StrCast(layoutDoc.transition), opacity: Cast(opacity, "number", null),
+ transition: StrCast(layoutDoc.transition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -1104,10 +1115,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
replica={entry[1].replica}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
- pointerEvents={
- this.backgroundActive ?
- true :
- (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
+ pointerEvents={this.backgroundActive || this.props.childPointerEvents ?
+ true :
+ (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
//fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
@@ -1199,6 +1209,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
@@ -1330,8 +1341,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
- easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ <CollectionFreeFormViewPannableContents
+ centeringShiftX={this.centeringShiftX}
+ centeringShiftY={this.centeringShiftY}
+ easing={this.easing}
+ transition={Cast(this.layoutDoc.transition, "string", null)}
+ viewDefDivClick={this.props.viewDefDivClick}
+ zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
</CollectionFreeFormViewPannableContents>
{this._timelineVisible ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
@@ -1371,18 +1387,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
{!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />
- {this.isAnnotationOverlay || !this.props.isSelected() || this.props.Document._viewType === CollectionViewType.Pile ? (null) :
- <>
- <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
- </div>
- <div key="num" className="numKeyframe" >
- {NumCast(this.props.Document.currentTimecode)}
- </div>
- <div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
- </div>
- </>}
+
<div className={"pullpane-indicator"}
style={{
display: this._pullDirection ? "block" : "none",
@@ -1424,7 +1429,7 @@ interface CollectionFreeFormViewPannableContentsProps {
easing: () => boolean;
viewDefDivClick?: ScriptField;
children: () => JSX.Element[];
- shifted: boolean;
+ transition?: string;
}
@observer
@@ -1438,8 +1443,8 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const zoom = this.props.zoomScaling();
return <div className={freeformclass}
style={{
- width: this.props.shifted ? 0 : undefined, height: this.props.shifted ? 0 : undefined,
- transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`
+ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`,
+ transition: this.props.transition
}}>
{this.props.children()}
</div>;
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
new file mode 100644
index 000000000..a7f4d4e53
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -0,0 +1,36 @@
+.antimodeMenu-button {
+ .color-preview {
+ width: 100%;
+ height: 100%;
+ }
+
+
+}
+
+.sketch-picker {
+ background: #323232;
+
+ .flexbox-fit {
+ background: #323232;
+ }
+}
+
+.btn-group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ /* Make the buttons appear below each other */
+}
+
+.btn2-group {
+ display: block;
+ background: #323232;
+ grid-template-columns: auto;
+
+ /* Make the buttons appear below each other */
+ .antimodeMenu-button {
+ background: #323232;
+ display: block;
+
+
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
new file mode 100644
index 000000000..5a27f74e5
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -0,0 +1,140 @@
+import React = require("react");
+import AntimodeMenu from "../../AntimodeMenu";
+import { observer } from "mobx-react";
+import { observable, action, computed } from "mobx";
+import "./InkOptionsMenu.scss";
+import { ActiveInkColor, ActiveInkBezierApprox, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox } from "../../InkingStroke";
+import { Scripting } from "../../../util/Scripting";
+import { InkTool } from "../../../../fields/InkField";
+import { ColorState } from "react-color";
+import { Utils } from "../../../../Utils";
+import GestureOverlay from "../../GestureOverlay";
+import { Doc } from "../../../../fields/Doc";
+
+@observer
+export default class InkOptionsMenu extends AntimodeMenu {
+ static Instance: InkOptionsMenu;
+
+ private _palette = ["D0021B", "F5A623", "F8E71C", "8B572A", "7ED321", "417505", "9013FE", "4A90E2", "50E3C2", "B8E986", "000000", "4A4A4A", "9B9B9B", "FFFFFF"];
+ private _width = ["1", "5", "10", "100", "200", "300"];
+ private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
+ private _icons = ["O", "∆", "ロ", "➜", "-"];
+
+ @observable _colorBtn = false;
+ @observable _widthBtn = false;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ InkOptionsMenu.Instance = this;
+ this._canFade = false; // don't let the inking menu fade away
+ }
+
+ @action
+ changeColor = (color: string) => {
+ const col: ColorState = {
+ hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+ rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+ };
+ SetActiveInkColor(Utils.colorString(col));
+ }
+
+ @action
+ changeBezier = (e: React.PointerEvent): void => {
+ SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
+ }
+
+ @computed get widthPicker() {
+ var widthPicker = <button
+ className="antimodeMenu-button"
+ key="width"
+ onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+ W
+ </button>;
+ if (this._widthBtn) {
+ widthPicker = <div className="btn2-group" key="width">
+ {widthPicker}
+ {this._width.map(wid => {
+ return <button
+ className="antimodeMenu-button"
+ key={wid}
+ onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; })}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+ {wid}
+ </button>;
+ })}
+ </div>;
+ }
+ return widthPicker;
+ }
+
+ @computed get colorPicker() {
+ var colorPicker = <button
+ className="antimodeMenu-button"
+ key="color"
+ title="colorChanger"
+ onPointerDown={action(e => this._colorBtn = !this._colorBtn)}
+ style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
+ <div className="color-preview" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
+ </button>;
+ if (this._colorBtn) {
+ colorPicker = <div className="btn-group" key="color">
+ {colorPicker}
+ {this._palette.map(color => {
+ return <button
+ className="antimodeMenu-button"
+ key={color}
+ onPointerDown={action(() => { this.changeColor(color); this._colorBtn = false; })}
+ style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
+ <div className="color-preview" style={{ backgroundColor: color }}></div>
+ </button>;
+ })}
+ </div>;
+ }
+ return colorPicker;
+ }
+
+ @computed get shapeButtons() {
+ return <>
+ {this._buttons.map((btn, i) => <button
+ className="antimodeMenu-button"
+ title={`Draw ${btn}`}
+ key={btn}
+ onPointerDown={action(e => GestureOverlay.Instance.InkShape = btn)}
+ style={{ backgroundColor: btn === GestureOverlay.Instance.InkShape ? "121212" : "" }}>
+ {this._icons[i]}
+ </button>)},
+ </>;
+ }
+
+ @computed get bezierButton() {
+ return <button
+ className="antimodeMenu-button"
+ title="Bezier changer"
+ key="bezier"
+ onPointerDown={e => this.changeBezier(e)}
+ style={ { backgroundColor:ActiveInkBezierApprox() ? "121212":"" } }>
+ B
+ </button>;
+ }
+
+ render() {
+ const buttons = [
+ <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}> ✜ </button>,
+ this.shapeButtons,
+ this.bezierButton,
+ this.widthPicker,
+ this.colorPicker,
+ ];
+ return this.getElement(buttons);
+ }
+}
+Scripting.addGlobal(function activatePen(penBtn: any) {
+ if (penBtn) {
+ Doc.SetSelectedTool(InkTool.Pen);
+ InkOptionsMenu.Instance.jumpTo(300, 300);
+ } else {
+ Doc.SetSelectedTool(InkTool.None);
+ InkOptionsMenu.Instance.fadeOut(true);
+ }
+}); \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c99e74ccd..73dd41a15 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -42,6 +42,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@observable _downY: number = 0;
@observable _visible: boolean = false;
_commandExecuted = false;
+ @observable _pointsX: number[] = [];
+ @observable _pointsY: number[] = [];
+ @observable _freeHand: boolean = false;
componentDidMount() {
this.props.setPreviewCursor?.(this.setPreviewCursor);
@@ -57,6 +60,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (hideMarquee) {
this._visible = false;
}
+ this._pointsX = [];
+ this._pointsY = [];
+ this._freeHand = false;
}
@undoBatch
@@ -191,6 +197,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
onPointerMove = (e: PointerEvent): void => {
this._lastX = e.pageX;
this._lastY = e.pageY;
+ this._pointsX.push(e.clientX);
+ this._pointsY.push(e.clientY);
if (!e.cancelBubble) {
if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
@@ -334,9 +342,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
_LODdisable: true,
title: "a nested collection",
});
- // const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
- // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
- // this.marqueeInkDelete(inkData);
+ selected.forEach(d => d.context = newCollection);
this.hideMarquee();
return newCollection;
}
@@ -359,10 +365,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const selected = this.marqueeSelect(false);
if (e instanceof KeyboardEvent ? e.key === "c" : true) {
selected.map(action(d => {
- //this.props.removeDocument(d);
- d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
- d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d.displayTimecode; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ d.x = dx - bounds.left - bounds.width / 2;
+ d.y = dy - bounds.top - bounds.height / 2;
return d;
}));
this.props.removeDocument(selected);
@@ -517,6 +527,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
this.cleanupInteractions(false);
}
+ if (e.key === "r") {
+ this._commandExecuted = true;
+ e.stopPropagation();
+ e.preventDefault();
+ this.changeFreeHand(true);
+ }
+ }
+
+ @action
+ changeFreeHand = (x: boolean) => {
+ this._freeHand = x;
}
// @action
// marqueeInkSelect(ink: Map<any, any>) {
@@ -557,7 +578,51 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// this.ink = new InkField(idata);
// }
// }
+ touchesLine(r1: { left: number, top: number, width: number, height: number }) {
+ for (var i = 0; i < this._pointsX.length; i++) {
+ const topLeft = this.props.getTransform().transformPoint(this._pointsX[i], this._pointsY[i]);
+ if (topLeft[0] > r1.left &&
+ topLeft[0] < r1.left + r1.width &&
+ topLeft[1] > r1.top &&
+ topLeft[1] < r1.top + r1.height) {
+ return true;
+ }
+ }
+ return false;
+ }
+ boundingShape(r1: { left: number, top: number, width: number, height: number }) {
+ const trueLeft = this.props.getTransform().transformPoint(Math.min(...this._pointsX), Math.min(...this._pointsY))[0];
+ const trueTop = this.props.getTransform().transformPoint(Math.min(...this._pointsX), Math.min(...this._pointsY))[1];
+ const trueRight = this.props.getTransform().transformPoint(Math.max(...this._pointsX), Math.max(...this._pointsY))[0];
+ const trueBottom = this.props.getTransform().transformPoint(Math.max(...this._pointsX), Math.max(...this._pointsY))[1];
+
+ if (r1.left > trueLeft && r1.top > trueTop && r1.left + r1.width < trueRight && r1.top + r1.height < trueBottom) {
+ var hasTop = false;
+ var hasLeft = false;
+ var hasBottom = false;
+ var hasRight = false;
+ for (var i = 0; i < this._pointsX.length; i++) {
+ const truePoint = this.props.getTransform().transformPoint(this._pointsX[i], this._pointsY[i]);
+ if (!hasLeft && (truePoint[0] > trueLeft && truePoint[0] < r1.left) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height)) {
+ hasLeft = true;
+ }
+ if (!hasTop && (truePoint[1] > trueTop && truePoint[1] < r1.top) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width)) {
+ hasTop = true;
+ }
+ if (!hasRight && (truePoint[0] < trueRight && truePoint[0] > r1.left + r1.width) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height)) {
+ hasRight = true;
+ }
+ if (!hasBottom && (truePoint[1] < trueBottom && truePoint[1] > r1.top + r1.height) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width)) {
+ hasBottom = true;
+ }
+ if (hasTop && hasLeft && hasBottom && hasRight) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
marqueeSelect(selectBackgrounds: boolean = true) {
const selRect = this.Bounds;
const selection: Doc[] = [];
@@ -567,8 +632,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const y = NumCast(doc.y);
const w = NumCast(layoutDoc._width);
const h = NumCast(layoutDoc._height);
- if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
- selection.push(doc);
+ if (this._freeHand === false) {
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+ selection.push(doc);
+ }
+ } else {
+ if (this.touchesLine({ left: x, top: y, width: w, height: h }) ||
+ this.boundingShape({ left: x, top: y, width: w, height: h })) {
+ selection.push(doc);
+ }
}
});
if (!selection.length && selectBackgrounds) {
@@ -595,8 +667,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const y = NumCast(doc.y);
const w = NumCast(layoutDoc._width);
const h = NumCast(layoutDoc._height);
- if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
- selection.push(doc);
+ if (this._freeHand === false) {
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+ selection.push(doc);
+ }
+ } else {
+ if (this.touchesLine({ left: x, top: y, width: w, height: h }) ||
+ this.boundingShape({ left: x, top: y, width: w, height: h })) {
+ selection.push(doc);
+ }
}
});
}
@@ -605,20 +684,47 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@computed
get marqueeDiv() {
- const p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
+ const p = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
const v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
/**
* @RE - The commented out span below
* This contains the "C for collection, ..." text on marquees.
* Commented out by syip2 when the marquee menu was added.
*/
- return <div className="marquee" style={{
- transform: `translate(${p[0]}px, ${p[1]}px)`,
- width: `${Math.abs(v[0])}`,
- height: `${Math.abs(v[1])}`, zIndex: 2000
- }} >
- {/* <span className="marquee-legend" /> */}
- </div>;
+ if (!this._freeHand) {
+ return <div className="marquee" style={{
+ transform: `translate(${p[0]}px, ${p[1]}px)`,
+ width: `${Math.abs(v[0])}`,
+ height: `${Math.abs(v[1])}`, zIndex: 2000
+ }} >
+ {/* <span className="marquee-legend" /> */}
+ </div>;
+
+ } else {
+ //subtracted 250 for offset
+ var str: string = "";
+ for (var i = 0; i < this._pointsX.length; i++) {
+ var x = 0;
+ x = this._pointsX[i] - 250;
+ str += x.toString();
+ str += ",";
+ str += this._pointsY[i].toString();
+ str += (" ");
+ }
+
+ //hardcoded height and width.
+ return <div className="marquee" style={{ zIndex: 2000 }}>
+ <svg height={2000} width={2000}>
+ <polyline
+ points={str}
+ fill="none"
+ stroke="black"
+ strokeWidth="1"
+ strokeDasharray="3"
+ />
+ </svg>
+ </div>;
+ }
}
render() {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 5fb4cf3c6..13b9a2459 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -298,7 +298,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor">
{this.props.hideback ? (null) : <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
<div className="linkEditor-info">
- <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p>
+ <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
<button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
</div>
{groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a4120f958..910aa744d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -69,19 +69,24 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
public static getValues(doc: Doc, time: number) {
+ const timecode = Math.round(time);
return ({
- x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= time && x !== undefined) || p === undefined ? x : p, undefined as any as number),
- y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= time && y !== undefined) || p === undefined ? y : p, undefined as any as number),
- opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= time || p === undefined ? o : p, undefined as any as number),
+ x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
+ y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
+ opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
});
}
- public static setValues(timecode: number, d: Doc, x?: number, y?: number, opacity?: number) {
+ public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
+ const timecode = Math.round(time);
+ Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number;
+ Cast(d["y-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = y as any as number;
Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
- Cast(d["y-indexed"], listSpec("number"), null)[timecode] = y as any as number;
+ Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number;
Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
}
- public static updateKeyframe(docs: Doc[], timecode: number) {
+ public static updateKeyframe(docs: Doc[], time: number) {
+ const timecode = Math.round(time);
docs.forEach(doc => {
const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
@@ -91,27 +96,30 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number);
doc.transition = "all 1s";
});
- setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
+ setTimeout(() => docs.forEach(doc => doc.transition = "inherit"), 1010);
}
public static gotoKeyframe(docs: Doc[]) {
docs.forEach(doc => doc.transition = "all 1s");
- setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
+ setTimeout(() => docs.forEach(doc => doc.transition = "inherit"), 1010);
}
- public static setupKeyframes(docs: Doc[], timecode: number, collection: Doc) {
+ public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) {
docs.forEach((doc, i) => {
+ const curTimecode = progressivize ? i : timecode;
const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
- xlist[Math.max(i - 1)] = xlist[timecode + 1] = NumCast(doc.x);
- ylist[Math.max(i - 1)] = ylist[timecode + 1] = NumCast(doc.y);
+ const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1));
+ xlist[Math.max(curTimecode - 1, 0)] = xlist[curTimecode] = NumCast(doc.x);
+ ylist[Math.max(curTimecode - 1, 0)] = ylist[curTimecode] = NumCast(doc.y);
doc["x-indexed"] = xlist;
doc["y-indexed"] = ylist;
- doc["opacity-indexed"] = new List<number>(numberRange(timecode).map(i => 1));
- doc.displayTimecode = ComputedField.MakeFunction("collection ? collection.currentTimecode : 0", {}, { collection });
- doc.x = ComputedField.MakeInterpolated("x", "displayTimecode");
- doc.y = ComputedField.MakeInterpolated("y", "displayTimecode");
- doc.opacity = ComputedField.MakeInterpolated("opacity", "displayTimecode");
+ doc["opacity-indexed"] = olist;
+ doc.activeFrame = ComputedField.MakeFunction("self.context ? (self.context.currentFrame||0) : 0");
+ doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
+ doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
+ doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
+ doc.transition = "inherit";
});
}
@@ -146,6 +154,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this.width,
height: this.height,
zIndex: this.ZInd,
+ mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
display: this.ZInd === -99 ? "none" : undefined,
pointerEvents: this.props.Document.isBackground || this.Opacity === 0 ? "none" : this.props.pointerEvents ? "all" : undefined
}} >
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 6d53915ea..0d6258cf3 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -1,15 +1,20 @@
import React = require("react");
+import { action } from "mobx";
import { observer } from "mobx-react";
-import { SketchPicker } from 'react-color';
+import { ColorState, SketchPicker } from 'react-color';
+import { Doc } from "../../../fields/Doc";
+import { Utils } from "../../../Utils";
import { documentSchema } from "../../../fields/documentSchemas";
+import { InkTool } from "../../../fields/InkField";
import { makeInterface } from "../../../fields/Schema";
import { StrCast } from "../../../fields/Types";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
import { ViewBoxBaseComponent } from "../DocComponent";
-import { InkingControl } from "../InkingControl";
+import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox } from "../InkingStroke";
import "./ColorBox.scss";
import { FieldView, FieldViewProps } from './FieldView';
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
type ColorDocument = makeInterface<[typeof documentSchema]>;
const ColorDocument = makeInterface(documentSchema);
@@ -18,18 +23,48 @@ const ColorDocument = makeInterface(documentSchema);
export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument>(ColorDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+ @undoBatch
+ @action
+ static switchColor(color: ColorState) {
+ Doc.UserDoc().backgroundColor = Utils.colorString(color);
+ SetActiveInkColor(color.hex);
+
+ if (Doc.GetSelectedTool() === InkTool.None) {
+ const selected = SelectionManager.SelectedDocuments();
+ selected.map(view => {
+ const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
+ view.props.Document.layout instanceof Doc ? view.props.Document.layout :
+ view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
+ if (targetDoc) {
+ if (StrCast(Doc.Layout(view.props.Document).layout).indexOf("FormattedTextBox") !== -1 && FormattedTextBox.HadSelection) {
+ Doc.Layout(view.props.Document).color = Doc.UserDoc().bacgroundColor;
+ } else {
+ Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
+ }
+ }
+ });
+ }
+ }
+
+ constructor(props: any) {
+ super(props);
+ }
render() {
const selDoc = SelectionManager.SelectedDocuments()?.[0]?.rootDoc;
return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
- <SketchPicker onChange={InkingControl.Instance.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- color={StrCast(CurrentUserUtils.ActivePen ? CurrentUserUtils.ActivePen.backgroundColor : undefined,
+ <SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ color={StrCast(ActiveInkPen()?.backgroundColor,
StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} />
<div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
- <div>{InkingControl.Instance.selectedWidth ?? 2}</div>
- <input type="range" value={InkingControl.Instance.selectedWidth ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchWidth(e.target.value)} />
+ <div> {ActiveInkWidth() ?? 2}</div>
+ <input type="range" value={ActiveInkWidth() ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveInkWidth(e.target.value)} />
+ <div> {ActiveInkBezierApprox() ?? 2}</div>
+ <input type="range" value={ActiveInkBezierApprox() ?? 2} defaultValue={2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
+ <br />
+ <br />
</div>
</div>;
}
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index 7849c9976..810a824cf 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -1,4 +1,5 @@
-.comparisonBox-interactive, .comparisonBox {
+.comparisonBox-interactive,
+.comparisonBox {
border-radius: inherit;
width: 100%;
height: 100%;
@@ -19,13 +20,13 @@
}
}
- .slide-bar {
+ .slide-bar {
position: absolute;
height: 100%;
width: 3px;
display: inline-block;
background: white;
- cursor: ew-resize;
+
.slide-handle {
position: absolute;
display: none;
@@ -33,7 +34,9 @@
width: 30px;
bottom: 0px;
left: -10.5px;
- .left-handle, .right-handle{
+
+ .left-handle,
+ .right-handle {
width: 15px;
}
}
@@ -77,10 +80,13 @@
}
}
}
+
.comparisonBox-interactive {
pointer-events: unset;
+ cursor: ew-resize;
+
.slide-bar {
- .slide-handle {
+ .slide-handle {
display: flex;
}
}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 77e07ec0c..cce60628d 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, Lambda, IReactionDisposer } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, Opt } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
import { createSchema, makeInterface } from '../../../fields/Schema';
import { NumCast, Cast, StrCast } from '../../../fields/Types';
@@ -32,13 +32,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
this._disposers[disposerId]?.();
if (ele) {
+ // create disposers identified by disposerId to remove drag & drop listeners
this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc);
}
}
@undoBatch
private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
- event.stopPropagation();
+ event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
if (droppedDocs?.length) {
this.dataDoc[fieldKey] = droppedDocs[0];
@@ -47,6 +48,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => {
+ // on click, animate slider movement to the targetWidth
this._animating = "all 1s";
this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth();
setTimeout(action(() => this._animating = ""), 1000);
@@ -64,7 +66,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
@undoBatch
clearDoc = (e: React.MouseEvent, fieldKey: string) => {
- e.stopPropagation;
+ e.stopPropagation; // prevent click event action (slider movement) in registerSliding
delete this.dataDoc[fieldKey];
}
@@ -72,7 +74,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
const childProps: DocumentViewProps = { ...this.props, pointerEvents: false, parentActive: this.props.active };
const clearButton = (which: string) => {
- return <div className={`clear-button ${which}`} onPointerDown={e => e.stopPropagation()} onClick={e => this.clearDoc(e, `${which}Doc`)}>
+ return <div className={`clear-button ${which}`}
+ onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
+ onClick={e => this.clearDoc(e, `${which}Doc`)}>
<FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
</div>;
};
@@ -95,13 +99,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
};
return (
- <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}`}>
+ <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
{displayBox("after", 1, this.props.PanelWidth() - 5)}
<div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
{displayBox("before", 0, 5)}
</div>
- <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)` }}>
+ <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)` }}
+ onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ >
<div className="slide-handle" />
</div>
</div >);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ef56e6fcd..126e9ac14 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, Field } from "../../../fields/Doc";
+import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc";
import { Cast, StrCast, NumCast } from "../../../fields/Types";
import { OmitKeys, Without, emptyPath } from "../../../Utils";
import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
@@ -185,7 +185,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
const bindings = this.CreateBindings(onClick, onInput);
// layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
- return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
+ return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || this.layoutDoc[AclSym] === AclPrivate) ? (null) :
<ObserverJsxParser
key={42}
blacklistedAttrs={[]}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0d06a8828..f9f453227 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -37,7 +37,6 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
-import { InkingControl } from '../InkingControl';
import { KeyphraseQueryView } from '../KeyphraseQueryView';
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
@@ -45,6 +44,7 @@ import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
+import { undo } from 'prosemirror-history';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -332,7 +332,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
} else func();
} else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
const alias = Doc.MakeAlias(this.props.Document);
- Doc.makeCustomViewClicked(alias, undefined, "onClick");
+ DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
this.props.addDocTab(alias, "onRight");
// UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
//ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
@@ -515,7 +515,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// console.log(e.button)
// console.log(e.nativeEvent)
// continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
- if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
// TODO: check here for panning/inking
@@ -546,7 +546,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerMove = (e: PointerEvent): void => {
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
- if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) return;
+ if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return;
if (e.cancelBubble && this.active) {
document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
}
@@ -684,6 +684,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
}
+ @undoBatch
+ @action
+ setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly") => {
+ this.layoutDoc.ACL = this.dataDoc.ACL = acl;
+ DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
+ if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
+ const data = d[DataSym];
+ if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
+ });
+ }
+
@action
onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
@@ -729,7 +740,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
@@ -744,6 +755,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const more = cm.findByDescription("More...");
console.log("[IN MORE] what is more? " + more)
const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
+ moreItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
+ moreItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
+ moreItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
@@ -751,7 +765,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!ClientUtils.RELEASE) {
// let copies: ContextMenuProps[] = [];
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
// cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
}
@@ -987,9 +1001,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
}
childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
- panelWidth = () => this.props.PanelWidth();
- panelHeight = () => this.props.PanelHeight();
- screenToLocalTransform = () => this.props.ScreenToLocalTransform();
@computed get contents() {
TraceMobx();
return (<>
@@ -1010,10 +1021,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.screenToLocalTransform}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
renderDepth={this.props.renderDepth}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
ignoreAutoHeight={this.props.ignoreAutoHeight}
focus={this.props.focus}
parentActive={this.props.parentActive}
@@ -1113,14 +1124,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get ignorePointerEvents() {
return this.props.pointerEvents === false ||
(this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
- (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ (this.Document.type === DocumentType.INK && Doc.GetSelectedTool() !== InkTool.None);
}
@undoBatch
@action
setCustomView = (custom: boolean, layout: string): void => {
Doc.setNativeView(this.props.Document);
if (custom) {
- Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+ DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
}
}
@observable _animateScalingTo = 0;
@@ -1144,6 +1155,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
render() {
+ if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
if (!(this.props.Document instanceof Doc)) return (null);
const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6913dfbc7..cabf30c13 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -192,7 +192,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
extractFaces = () => {
const converter = (results: any) => {
- return results.map((face: CognitiveServices.Image.Face) => Docs.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
+ return results.map((face: CognitiveServices.Image.Face) => Doc.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
};
this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter);
}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 956d6556b..3cbe3e494 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -99,9 +99,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
<div className="keyValuePair-td-key-container">
<button style={hover} className="keyValuePair-td-key-delete" onClick={undoBatch(() => {
if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
- props.Document[props.fieldKey] = undefined;
+ delete props.Document[props.fieldKey];
}
- else props.Document.proto![props.fieldKey] = undefined;
+ else delete props.Document.proto![props.fieldKey];
})}>
X
</button>
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 493f23dc4..fae216f17 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -248,7 +248,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
_pdfjsRequested = false;
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
- if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document.scrollY !== undefined) this._everActive = true;
+ if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document._scrollY !== undefined) this._everActive = true;
if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
if (pdfUrl instanceof PdfField && this._pdf) {
return this.renderPdfView;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index aeb77a894..dbc879920 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -11,7 +11,6 @@ import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./PresBox.scss";
import { ViewBoxBaseComponent } from "../DocComponent";
@@ -20,6 +19,7 @@ import { Docs } from "../../documents/Documents";
import { PrefetchProxy } from "../../../fields/Proxy";
import { ScriptField } from "../../../fields/ScriptField";
import { Scripting } from "../../util/Scripting";
+import { InkingStroke } from "../InkingStroke";
type PresBoxSchema = makeInterface<[typeof documentSchema]>;
const PresBoxDocument = makeInterface(documentSchema);
@@ -59,10 +59,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
next = () => {
this.updateCurrentPresentation();
const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
- const lastFrame = Cast(presTargetDoc.lastTimecode, "number", null);
- const curFrame = NumCast(presTargetDoc.currentTimecode);
+ const lastFrame = Cast(presTargetDoc.lastFrame, "number", null);
+ const curFrame = NumCast(presTargetDoc.currentFrame);
if (lastFrame !== undefined && curFrame < lastFrame) {
- presTargetDoc.currentTimecode = curFrame + 1;
+ presTargetDoc.transition = "all 1s";
+ setTimeout(() => presTargetDoc.transition = undefined, 1010);
+ presTargetDoc.currentFrame = curFrame + 1;
}
else if (this.childDocs[this.itemIndex + 1] !== undefined) {
let nextSelected = this.itemIndex + 1;
@@ -199,8 +201,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
- if (presTargetDoc.lastTimecode !== undefined) {
- presTargetDoc.currentTimecode = 0;
+ if (presTargetDoc.lastFrame !== undefined) {
+ presTargetDoc.currentFrame = 0;
}
if (!this.layoutDoc.presStatus) {
@@ -291,7 +293,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex));
getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
panelHeight = () => this.props.PanelHeight() - 20;
- active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
+ active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground) &&
(this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
render() {
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 29e3c008a..d75b864cf 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -5,21 +5,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as rp from 'request-promise';
+import { Doc } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface, listSpec } from "../../../fields/Schema";
+import { listSpec, makeInterface } from "../../../fields/Schema";
import { Cast, NumCast } from "../../../fields/Types";
import { VideoField } from "../../../fields/URLField";
-import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { emptyFunction, returnFalse, returnOne, returnZero, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxBaseComponent } from "../DocComponent";
-import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
-import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
-import { OverlayView } from "../OverlayView";
const path = require('path');
type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
@@ -134,7 +132,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
}
@computed get content() {
- const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
+ const interactive = Doc.GetSelectedTool() || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + interactive;
return <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9d02239fc..1e0a859d3 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -15,7 +15,6 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
-import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
import { documentSchema } from "../../../fields/documentSchemas";
@@ -229,7 +228,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
- const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
+ const interactive = Doc.GetSelectedTool() || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
<video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
@@ -273,8 +272,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
this._reactionDisposer = reaction(() => this.layoutDoc.currentTimecode, () => !this._playing && this.Seek((this.layoutDoc.currentTimecode || 0)));
- this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
- const interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting;
+ this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, Doc.GetSelectedTool()], () => {
+ const interactive = Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
}, { fireImmediately: true });
};
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f80cea941..fb9e57b51 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -15,7 +15,6 @@ import { DragManager } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
-import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./WebBox.scss";
import React = require("react");
@@ -58,15 +57,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
iframe.contentDocument.addEventListener('pointerdown', this.iframedown, false);
iframe.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
this.layoutDoc.scrollHeight = iframe.contentDocument.children?.[0].scrollHeight || 1000;
- iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
- iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc.scrollLeft);
+ iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc._scrollTop);
+ iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft);
}
this._reactionDisposer?.();
- this._reactionDisposer = reaction(() => ({ y: this.layoutDoc.scrollY, x: this.layoutDoc.scrollX }),
+ this._reactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
({ x, y }) => {
if (y !== undefined) {
this._outerRef.current!.scrollTop = y;
- this.layoutDoc.scrollY = undefined;
+ this.layoutDoc._scrollY = undefined;
}
if (x !== undefined) {
this._outerRef.current!.scrollLeft = x;
@@ -83,8 +82,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
iframeScrolled = (e: any) => {
const scrollTop = e.target?.children?.[0].scrollTop;
const scrollLeft = e.target?.children?.[0].scrollLeft;
- this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scrollTop;
- this.layoutDoc.scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft;
+ this.layoutDoc._scrollTop = this._outerRef.current!.scrollTop = scrollTop;
+ this.layoutDoc._scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft;
}
async componentDidMount() {
const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
@@ -106,7 +105,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} // else it's an HTMLfield
} else if (field?.url) {
const result = await WebRequest.get(Utils.CorsProxy(field.url.href));
- this.dataDoc.text = htmlToText.fromString(result.content);
+ if (result) {
+ this.dataDoc.text = htmlToText.fromString(result.content);
+ }
}
}
@@ -409,9 +410,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href;
- view = <iframe className="webBox-iframe" ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} />;
+ view = <iframe className="webBox-iframe" enable-annotation={true} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} />;
} else {
- view = <iframe className="webBox-iframe" ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />;
+ view = <iframe className="webBox-iframe" enable-annotation={true} ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />;
}
return view;
}
@@ -423,7 +424,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const frozen = !this.props.isSelected() || decInteracting;
return (<>
- <div className={"webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "")}
+ <div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !decInteracting ? "-interactive" : "")}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{view}
</div>;
@@ -438,7 +439,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
{this.urlEditor()}
</>);
}
- scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc.scrollLeft), NumCast(this.layoutDoc.scrollTop));
+ scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
render() {
return (<div className={`webBox-container`}
style={{
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 477a2ca08..49114d967 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -95,6 +95,17 @@
.formattedTextBox-inner {
height: 100%;
white-space: pre-wrap;
+ hr {
+ display: block;
+ unicode-bidi: isolate;
+ margin-block-start: 0.5em;
+ margin-block-end: 0.5em;
+ margin-inline-start: auto;
+ margin-inline-end: auto;
+ overflow: hidden;
+ border-style: inset;
+ border-width: 1px;
+ }
}
// .menuicon {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 411eebf4f..130931b24 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -52,14 +52,12 @@ import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from "../../DocComponent";
import { DocumentButtonBar } from '../../DocumentButtonBar';
-import { InkingControl } from "../../InkingControl";
import { AudioBox } from '../AudioBox';
import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
-import { ScriptField } from '../../../../fields/ScriptField';
-import GoogleAuthenticationManager from '../../../apis/GoogleAuthenticationManager';
+import { InkingStroke } from '../../InkingStroke';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -479,11 +477,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
changeItems.push({
description: StrCast(note.title), event: undoBatch(() => {
Doc.setNativeView(this.rootDoc);
- Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+ DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
}), icon: "eye"
});
});
- changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
+ changeItems.push({ description: "FreeForm", event: undoBatch(() => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
!change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
}
@@ -721,7 +719,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
},
{ fireImmediately: true }
);
- this._disposers.scroll = reaction(() => NumCast(this.layoutDoc.scrollPos),
+ this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
);
@@ -869,7 +867,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.link, { href: Utils.prepend(`/doc/${linkId}`), location: "onRight", title: title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
@@ -1165,7 +1163,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onscrolled = (ev: React.UIEvent) => {
- this.layoutDoc.scrollPos = this._scrollRef.current!.scrollTop;
+ this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop;
}
@action
tryUpdateHeight(limitHeight?: number) {
@@ -1204,7 +1202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
TraceMobx();
const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
- const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
+ const interactive = Doc.GetSelectedTool() || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
} else if (FormattedTextBoxComment.textBox === this) {
@@ -1259,7 +1257,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
</div>
{!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
<div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> :
- <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
+ <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index d47ae63af..59a6045ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -183,7 +183,7 @@ export class FormattedTextBoxComment {
const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
if (anchor !== target && anchor && target) {
- target.scrollY = NumCast(anchor?.y);
+ target._scrollY = NumCast(anchor?.y);
}
if (target) {
ReactDOM.render(<ContentFittingDocumentView
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index dd610944a..444a8a4c0 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -141,15 +141,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (force || props.Document._singleLine) {
const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
- layoutKey,
- _singleLine: BoolCast(originalDoc._singleLine),
- x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
- });
+ const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
+ Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
return true;
@@ -168,15 +165,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (originalDoc instanceof Doc) {
const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
- layoutKey,
- _singleLine: BoolCast(originalDoc._singleLine),
- x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
- });
+ const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
+ Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
}
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 8541a3149..affffc44e 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -29,6 +29,7 @@
.page {
position: relative;
+ border: unset;
}
.pdfViewer-text-selected {
.textLayer{
@@ -57,6 +58,9 @@
display: inline-block;
width:100%;
}
+ .pdfViewer-overlay {
+ pointer-events: none;
+ }
.pdfViewer-annotationLayer {
position: absolute;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 810ce5aea..e39e96607 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,7 +2,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
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, FieldResult, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
@@ -26,19 +25,18 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
import { CollectionView } from "../collections/CollectionView";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
-import { InkingControl } from "../InkingControl";
import Annotation from "./Annotation";
import PDFMenu from "./PDFMenu";
import "./PDFViewer.scss";
import React = require("react");
+import { SnappingManager } from "../../util/SnappingManager";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
+import { Networking } from "../../Network";
export const pageSchema = createSchema({
curPage: "number",
- fitWidth: "boolean",
rotation: "number",
- scrollY: "number",
scrollHeight: "number",
serachMatch: "boolean"
});
@@ -93,7 +91,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@observable private _showWaiting = true;
@observable private _showCover = false;
@observable private _zoomed = 1;
- @observable private _scrollTop = 0;
private _pdfViewer: any;
private _retries = 0; // number of times tried to create the PDF viewer
@@ -128,10 +125,20 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
// change the address to be the file address of the PNG version of each page
// file address of the pdf
const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
- const addr = Utils.prepend(`/thumbnail${this.props.url.substring("files/pdfs/".length, this.props.url.length - ".pdf".length)}-${(this.Document.curPage || 1)}.png`);
- this._coverPath = href.startsWith(window.location.origin) ? JSON.parse(await rp.get(addr)) : { width: 100, height: 100, path: "" };
+ const { url: relative } = this.props;
+ const pathComponents = relative.split("/pdfs/")[1].split("/");
+ const coreFilename = pathComponents.pop()!.split(".")[0];
+ const params: any = {
+ coreFilename,
+ pageNum: this.Document.curPage || 1,
+ };
+ if (pathComponents.length) {
+ params.subtree = `${pathComponents.join("/")}/`;
+ }
+ this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
runInAction(() => this._showWaiting = this._showCover = true);
this.props.startupLive && this.setupPdfJsViewer();
+ this._mainCont.current!.scrollTop = this.layoutDoc._scrollTop || 0;
this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
if (search) {
this.search(Doc.SearchQuery(), true);
@@ -147,14 +154,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
() => (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(),
{ fireImmediately: true });
this._reactionDisposer = reaction(
- () => this.Document.scrollY,
+ () => this.Document._scrollY,
(scrollY) => {
if (scrollY !== undefined) {
- if (this._showCover || this._showWaiting) {
- this.setupPdfJsViewer();
- }
- this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document.scrollY || 0));
- this.Document.scrollY = undefined;
+ (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+ this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0));
+ setTimeout(() => this.Document._scrollY = undefined, 1000);
}
},
{ fireImmediately: true }
@@ -209,7 +214,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
await this.initialLoad();
this._scrollTopReactionDisposer = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
- (stop) => (stop !== undefined) && this._mainCont.current && smoothScroll(500, this._mainCont.current, stop), { fireImmediately: true });
+ (stop) => (stop !== undefined && this.layoutDoc._scrollY === undefined) && (this._mainCont.current!.scrollTop = stop), { fireImmediately: true });
+
this._annotationReactionDisposer = reaction(
() => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]),
annotations => annotations?.length && (this._annotations = annotations),
@@ -341,7 +347,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onScroll = (e: React.UIEvent<HTMLElement>) => {
- this._scrollTop = this._mainCont.current!.scrollTop;
+ this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop);
this._pdfViewer && (this.Document.curPage = this._pdfViewer.currentPageNumber);
}
@@ -572,7 +578,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
clipDoc.rootDocument = targetDoc;
- Doc.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
+ DocUtils.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
targetDoc.layoutKey = "layout";
// const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
// Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
@@ -593,7 +599,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
scrollXf = () => {
- return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform();
+ return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform();
}
onClick = (e: React.MouseEvent) => {
this._setPreviewCursor &&
@@ -641,7 +647,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0);
panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
@computed get overlayLayer() {
- return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay"
+ return <div className={`pdfViewer-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} id="overlay"
style={{ transform: `scale(${this._zoomed})` }}>
<CollectionFreeFormView {...this.props}
LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
@@ -661,6 +667,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
ContentScaling={this.contentZoom}
bringToFront={emptyFunction}
whenActiveChanged={this.whenActiveChanged}
+ childPointerEvents={true}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 526a3dbf4..caee06d8f 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -101,12 +101,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
- if (this.rootDoc.presProgressivize && !rootTarget?.lastTimecode) {
- rootTarget.currentTimecode = 0;
- CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, this.presBox);
- rootTarget.lastTimecode = docs.length - 1;
+ if (this.rootDoc.presProgressivize) {
+ rootTarget.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
+ rootTarget.lastFrame = docs.length - 1;
}
- docs.forEach((d, i) => i && numberRange(i).forEach(f => Cast(d["opacity-indexed"], listSpec("number"), [])[f] = 0));
}
/**
@@ -179,6 +178,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
LibraryPath={emptyPath}
fitToBox={true}
+ backgroundColor={this.props.backgroundColor}
rootSelected={returnTrue}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index eea7b528b..c9d29e485 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -309,9 +309,9 @@ export class SearchBox extends React.Component<SearchProps> {
const baseExpr = "NOT baseProto_b:true";
const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true";
const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox";
- const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`;
+ // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // this line was causing issues for me, check solr logging -syip2
// fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello
- const query = [baseExpr, includeDeleted, includeIcons, typeExpr].join(" AND ").replace(/AND $/, "");
+ const query = [baseExpr, includeDeleted, includeIcons].join(" AND ").replace(/AND $/, "");
return query;
}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 2ea011316..83de9cc15 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -4,14 +4,13 @@ import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeForm
import { FieldViewProps, FieldView } from "../nodes/FieldView";
import { observable, action } from "mobx";
import { DocumentDecorations } from "../DocumentDecorations";
-import { InkingControl } from "../InkingControl";
import "../../views/nodes/WebBox.scss";
import "./DashWebRTCVideo.scss";
-import adapter from 'webrtc-adapter';
import { initialize, hangup, refreshVideos } from "./WebCamLogic";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faSync, faPhoneSlash } from "@fortawesome/free-solid-svg-icons";
+import { Doc } from "../../../fields/Doc";
library.add(faSync);
library.add(faPhoneSlash);
@@ -73,8 +72,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
</div >;
const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
- const classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
-
+ const classname = "webBox-cont" + (this.props.isSelected() && !Doc.GetSelectedTool() && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
<>
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
index c847b8656..5f6202bc8 100644
--- a/src/client/views/webcam/WebCamLogic.js
+++ b/src/client/views/webcam/WebCamLogic.js
@@ -29,7 +29,7 @@ export function initialize(roomName, handlerUI) {
room = roomName;
- socket = io.connect(`${window.location.protocol}//${window.location.hostname}:${4321}`);
+ socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`);
if (room !== '') {
socket.emit('create or join', room);
@@ -104,9 +104,9 @@ export function initialize(roomName, handlerUI) {
navigator.mediaDevices.getUserMedia({
- audio: true,
- video: true
- })
+ audio: true,
+ video: true
+ })
.then(gotStream)
.catch(function (e) {
alert('getUserMedia() error: ' + e.name);
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index d541c8009..be53c0b9b 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -7,6 +7,7 @@ import { makeInterface } from '../fields/Schema';
import { ObjectField } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
import { DocServer } from '../client/DocServer';
+import { resolvedPorts } from '../client/views/Main';
@observer
class Repl extends React.Component {
@@ -61,6 +62,6 @@ class Repl extends React.Component {
}
(async function () {
- DocServer.init(window.location.protocol, window.location.hostname, 4321, "repl");
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "repl");
ReactDOM.render(<Repl />, document.getElementById("root"));
})(); \ No newline at end of file
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index ddddee3be..0ca067ed3 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -14,6 +14,7 @@ import { RichTextField } from '../fields/RichTextField';
import { DateField } from '../fields/DateField';
import { ScriptField } from '../fields/ScriptField';
import CursorField from '../fields/CursorField';
+import { resolvedPorts } from '../client/views/Main';
DateField;
URLField;
@@ -182,7 +183,7 @@ class Viewer extends React.Component {
}
(async function () {
- await DocServer.init(window.location.protocol, window.location.hostname, 4321, "viewer");
+ await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "viewer");
ReactDOM.render((
<div style={{ position: "absolute", width: "100%", height: "100%" }}>
<Viewer />
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1ea686cbb..6f712f2ed 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -7,18 +7,17 @@ import { Scripting, scriptingGlobal } from "../client/util/Scripting";
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
import { UndoManager } from "../client/util/UndoManager";
import { intersectRect, Utils } from "../Utils";
-import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols";
+import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
+import { InkTool } from "./InkField";
import { List } from "./List";
import { ObjectField } from "./ObjectField";
import { PrefetchProxy, ProxyField } from "./Proxy";
import { FieldId, RefField } from "./RefField";
import { RichTextField } from "./RichTextField";
import { listSpec } from "./Schema";
-import { ComputedField, ScriptField } from "./ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types";
+import { ComputedField } from "./ScriptField";
+import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
-import { Docs, DocumentOptions } from "../client/documents/Documents";
-import { PdfField, VideoField, AudioField, ImageField } from "./URLField";
import { LinkManager } from "../client/util/LinkManager";
export namespace Field {
@@ -93,13 +92,44 @@ export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
export const DataSym = Symbol("Data");
export const LayoutSym = Symbol("Layout");
+export const AclSym = Symbol("Acl");
+export const AclPrivate = Symbol("AclOwnerOnly");
+export const AclReadonly = Symbol("AclReadOnly");
+export const AclAddonly = Symbol("AclAddonly");
export const UpdatingFromServer = Symbol("UpdatingFromServer");
const CachedUpdates = Symbol("Cached updates");
function fetchProto(doc: Doc) {
+ if (doc.author !== Doc.CurrentUserEmail) {
+ const acl = Doc.Get(doc, "ACL", true);
+ switch (acl) {
+ case "ownerOnly":
+ doc[AclSym] = AclPrivate;
+ return undefined;
+ case "readOnly":
+ doc[AclSym] = AclReadonly;
+ break;
+ case "addOnly":
+ doc[AclSym] = AclAddonly;
+ break;
+ }
+ }
+
const proto = doc.proto;
if (proto instanceof Promise) {
+ proto.then(proto => {
+ if (proto.author !== Doc.CurrentUserEmail) {
+ if (proto.ACL === "ownerOnly") {
+ proto[AclSym] = doc[AclSym] = AclPrivate;
+ return undefined;
+ } else if (proto.ACL === "readOnly") {
+ proto[AclSym] = doc[AclSym] = AclReadonly;
+ } else if (proto.ACL === "addOnly") {
+ proto[AclSym] = doc[AclSym] = AclAddonly;
+ }
+ }
+ });
return proto;
}
}
@@ -113,10 +143,10 @@ export class Doc extends RefField {
set: setter,
get: getter,
// getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
- has: (target, key) => key in target.__fields,
+ has: (target, key) => target[AclSym] !== AclPrivate && key in target.__fields,
ownKeys: target => {
const obj = {} as any;
- Object.assign(obj, target.___fields);
+ (target[AclSym] !== AclPrivate) && Object.assign(obj, target.___fields);
runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
return Object.keys(obj);
},
@@ -170,8 +200,11 @@ export class Doc extends RefField {
private [Self] = this;
private [SelfProxy]: any;
+ public [AclSym]: any = undefined;
public [WidthSym] = () => NumCast(this[SelfProxy]._width);
public [HeightSym] = () => NumCast(this[SelfProxy]._height);
+ public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
+ public [ToString]() { return `Doc(${this[AclSym] === AclPrivate ? "-inaccessible-" : this.title})`; }
public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
public get [DataSym]() {
const self = this[SelfProxy];
@@ -193,8 +226,6 @@ export class Doc extends RefField {
return undefined;
}
- [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
- [ToString]() { return `Doc(${this.title})`; }
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
public static CurrentUserEmail: string = "";
@@ -456,9 +487,82 @@ export namespace Doc {
}
alias.aliasOf = doc;
alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`);
+ alias.author = Doc.CurrentUserEmail;
return alias;
}
+
+
+ export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc {
+ if (Doc.IsBaseProto(doc)) return doc;
+ if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
+ const copy = new Doc(undefined, true);
+ cloneMap.set(doc[Id], copy);
+ if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
+ const exclude = Cast(doc.excludeFields, listSpec("string"), []);
+ Object.keys(doc).forEach(key => {
+ if (exclude.includes(key)) return;
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = ProxyField.WithoutProxy(() => doc[key]);
+ const copyObjectField = (field: ObjectField) => {
+ const list = Cast(doc[key], listSpec(Doc));
+ if (list !== undefined && !(list instanceof Promise)) {
+ copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs)));
+ } else if (doc[key] instanceof Doc) {
+ copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields
+ } else {
+ copy[key] = ObjectField.MakeCopy(field);
+ if (field instanceof RichTextField) {
+ if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
+ rtfs.push({ copy, key, field });
+ }
+ }
+ }
+ };
+ if (key === "proto") {
+ if (doc[key] instanceof Doc) {
+ copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs);
+ }
+ } else {
+ if (field instanceof RefField) {
+ copy[key] = field;
+ } else if (cfield instanceof ComputedField) {
+ copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
+ (key === "links" && field instanceof ObjectField) && copyObjectField(field);
+ } else if (field instanceof ObjectField) {
+ copyObjectField(field);
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happend...
+ } else {
+ copy[key] = field;
+ }
+ }
+ });
+ Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
+ copy.cloneOf = doc;
+ cloneMap.set(doc[Id], copy);
+ return copy;
+ }
+ export function MakeClone(doc: Doc): Doc {
+ const cloneMap = new Map<string, Doc>();
+ const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
+ const copy = Doc.makeClone(doc, cloneMap, rtfMap);
+ rtfMap.map(({ copy, key, field }) => {
+ const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
+ const mapped = cloneMap.get(id);
+ return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
+ };
+ const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
+ const mapped = cloneMap.get(id);
+ return href + (mapped ? mapped[Id] : id);
+ };
+ const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
+ const re = new RegExp(regex, "g");
+ copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ });
+ return copy;
+ }
+
//
// Determines whether the layout needs to be expanded (as a template).
// template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template
@@ -586,80 +690,10 @@ export namespace Doc {
}
}
});
-
+ copy["author"] = Doc.CurrentUserEmail;
return copy;
}
- export function MakeClone(doc: Doc): Doc {
- const cloneMap = new Map<string, Doc>();
- const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = Doc.makeClone(doc, cloneMap, rtfMap);
- rtfMap.map(({ copy, key, field }) => {
- const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
- const mapped = cloneMap.get(id);
- return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
- };
- const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
- const mapped = cloneMap.get(id);
- return href + (mapped ? mapped[Id] : id);
- };
- const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
- const re = new RegExp(regex, "g");
- copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
- });
- return copy;
- }
-
- export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc {
- if (Doc.IsBaseProto(doc)) return doc;
- if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
- const copy = new Doc(undefined, true);
- cloneMap.set(doc[Id], copy);
- if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
- const exclude = Cast(doc.excludeFields, listSpec("string"), []);
- Object.keys(doc).forEach(key => {
- if (exclude.includes(key)) return;
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = (field: ObjectField) => {
- const list = Cast(doc[key], listSpec(Doc));
- if (list !== undefined && !(list instanceof Promise)) {
- copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs)));
- } else if (doc[key] instanceof Doc) {
- copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields
- } else {
- copy[key] = ObjectField.MakeCopy(field);
- if (field instanceof RichTextField) {
- if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
- rtfs.push({ copy, key, field });
- }
- }
- }
- };
- if (key === "proto") {
- if (doc[key] instanceof Doc) {
- copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs);
- }
- } else {
- if (field instanceof RefField) {
- copy[key] = field;
- } else if (cfield instanceof ComputedField) {
- copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
- (key === "links" && field instanceof ObjectField) && copyObjectField(field);
- } else if (field instanceof ObjectField) {
- copyObjectField(field);
- } else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
- } else {
- copy[key] = field;
- }
- }
- });
- Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
- copy.cloneOf = doc;
- cloneMap.set(doc[Id], copy);
- return copy;
- }
export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
@@ -786,6 +820,9 @@ export namespace Doc {
export function SearchQuery(): string { return manager._searchQuery; }
export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
export function UserDoc(): Doc { return manager._user_doc; }
+
+ export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; }
+ export function GetSelectedTool(): InkTool { return (FieldValue(StrCast(Doc.UserDoc().activeInkTool)) ?? InkTool.None) as InkTool; }
export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
export function IsBrushed(doc: Doc) {
return computedFn(function IsBrushed(doc: Doc) {
@@ -794,6 +831,7 @@ export namespace Doc {
}
// don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
export function IsBrushedDegreeUnmemoized(doc: Doc) {
+ if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return 0;
return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
}
export function IsBrushedDegree(doc: Doc) {
@@ -802,11 +840,13 @@ export namespace Doc {
})(doc);
}
export function BrushDoc(doc: Doc) {
+ if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
brushManager.BrushedDoc.set(doc, true);
brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
return doc;
}
export function UnBrushDoc(doc: Doc) {
+ if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
brushManager.BrushedDoc.delete(doc);
brushManager.BrushedDoc.delete(Doc.GetProto(doc));
return doc;
@@ -836,6 +876,7 @@ export namespace Doc {
}
const highlightManager = new HighlightBrush();
export function IsHighlighted(doc: Doc) {
+ if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return false;
return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
}
export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
@@ -966,120 +1007,147 @@ export namespace Doc {
return false;
}
- // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
- export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
- const batch = UndoManager.StartBatch("makeCustomViewClicked");
- runInAction(() => {
- doc.layoutKey = "layout_" + templateSignature;
- if (doc[doc.layoutKey] === undefined) {
- createCustomView(doc, creator, templateSignature, docLayoutTemplate);
- }
- });
- batch.end();
- }
- export function findTemplate(templateName: string, type: string, signature: string) {
- let docLayoutTemplate: Opt<Doc>;
- const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
- const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
- const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
- const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
- const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
- // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
- // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
- return docLayoutTemplate;
- }
- export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
- const templateName = templateSignature.replace(/\(.*\)/, "");
- docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature);
-
- const customName = "layout_" + templateSignature;
- const _width = NumCast(doc._width);
- const _height = NumCast(doc._height);
- const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
-
- let fieldTemplate: Opt<Doc>;
- if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
- fieldTemplate = Docs.Create.TextDocument("", options);
- } else if (doc.data instanceof PdfField) {
- fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
- } else if (doc.data instanceof VideoField) {
- fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof AudioField) {
- fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof ImageField) {
- fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
- }
- const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
- docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
- }
- export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
- Doc.setNativeView(doc);
- if (custom) {
- makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
+ export namespace Get {
+
+ const primitives = ["string", "number", "boolean"];
+
+ export interface JsonConversionOpts {
+ data: any;
+ title?: string;
+ appendToExisting?: { targetDoc: Doc, fieldKey?: string };
+ excludeEmptyObjects?: boolean;
}
- }
- export function iconify(doc: Doc) {
- const layoutKey = Cast(doc.layoutKey, "string", null);
- Doc.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined);
- if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
- }
- export function pileup(docList: Doc[], x?: number, y?: number) {
- let w = 0, h = 0;
- runInAction(() => {
- docList.forEach(d => {
- Doc.iconify(d);
- w = Math.max(d[WidthSym](), w);
- h = Math.max(d[HeightSym](), h);
- });
- h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
- docList.forEach((d, i) => {
- d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
- d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
- d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- });
- });
- if (x !== undefined && y !== undefined) {
- const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
- newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
- newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
- newCollection._width = newCollection._height = 110;
- //newCollection.borderRounding = "40px";
- newCollection._jitterRotation = 10;
- newCollection._backgroundColor = "gray";
- newCollection._overflow = "visible";
- return newCollection;
+ const defaultKey = "json";
+
+ /**
+ * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
+ * deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
+ *
+ * After building a hierarchy within / below a top-level document, it then returns that top-level parent.
+ *
+ * If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the
+ * string is invalid JSON, so we should assume that the input is the result of a JSON.parse()
+ * call that returned a regular string value to be stored as a Field.
+ *
+ * If we've received something other than a string, since the caller might also pass in the results of a
+ * JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number.
+ * Anything else (like a function, etc. passed in naively as any) is meaningless for this operation.
+ *
+ * All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
+ * lacking the key value structure, gets stored as a field in a wrapper document.
+ *
+ * @param data for convenience and flexibility, either a valid JSON string to be parsed,
+ * or the result of any JSON.parse() call.
+ * @param title an optional title to give to the highest parent document in the hierarchy.
+ * If whether this function creates a new document or appendToExisting is specified and that document already has a title,
+ * because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
+ * @param appendToExisting **if specified**, there are two cases, both of which return the target document:
+ *
+ * 1) the json to be converted can be represented as a document, in which case the target document will act as the root
+ * of the tree and receive all the conversion results as new fields on itself
+ * 2) the json can't be represented as a document, in which case the function will assign the field-level conversion
+ * results to either the specified key on the target document, or to its "json" key by default.
+ *
+ * If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls)
+ * to act as the root of the tree.
+ *
+ * One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
+ * say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
+ * from a default call to new Doc.
+ *
+ * @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
+ * if they contain no data. By default, empty objects and arrays are ignored.
+ */
+ export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
+ if (excludeEmptyObjects === undefined) {
+ excludeEmptyObjects = true;
+ }
+ if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
+ return undefined;
+ }
+ let resolved: any;
+ try {
+ resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
+ } catch (e) {
+ return undefined;
+ }
+ let output: Opt<Doc>;
+ if (typeof resolved === "object" && !(resolved instanceof Array)) {
+ output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
+ } else {
+ const result = toField(resolved, excludeEmptyObjects);
+ if (appendToExisting) {
+ (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
+ } else {
+ (output = new Doc).json = result;
+ }
+ }
+ title && output && (output.title = title);
+ return output;
}
- }
+ /**
+ * For each value of the object, recursively convert it to its appropriate field value
+ * and store the field at the appropriate key in the document if it is not undefined
+ * @param object the object to convert
+ * @returns the object mapped from JSON to field values, where each mapping
+ * might involve arbitrary recursion (since toField might itself call convertObject)
+ */
+ const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
+ const hasEntries = Object.keys(object).length;
+ if (hasEntries || !excludeEmptyObjects) {
+ const resolved = target ?? new Doc;
+ if (hasEntries) {
+ let result: Opt<Field>;
+ Object.keys(object).map(key => {
+ // if excludeEmptyObjects is true, any qualifying conversions from toField will
+ // be undefined, and thus the results that would have
+ // otherwise been empty (List or Doc)s will just not be written
+ if (result = toField(object[key], excludeEmptyObjects, key)) {
+ resolved[key] = result;
+ }
+ });
+ }
+ title && (resolved.title = title);
+ return resolved;
+ }
+ };
- export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) {
- let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
- if (!(optionsCollection instanceof Doc)) {
- optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set` }, enumeratedFieldKey);
- Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
- }
- const options = optionsCollection as Doc;
- const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
- const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
- targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
- targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
- targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
- enumerations.map(enumeration => {
- const found = DocListCast(options.data).find(d => d.title === enumeration.title);
- if (found) {
- found._backgroundColor = enumeration._backgroundColor || found._backgroundColor;
- found._color = enumeration.color || found._color;
- } else {
- Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, enumeration));
+ /**
+ * For each element in the list, recursively convert it to a document or other field
+ * and push the field to the list if it is not undefined
+ * @param list the list to convert
+ * @returns the list mapped from JSON to field values, where each mapping
+ * might involve arbitrary recursion (since toField might itself call convertList)
+ */
+ const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
+ const target = new List();
+ let result: Opt<Field>;
+ // if excludeEmptyObjects is true, any qualifying conversions from toField will
+ // be undefined, and thus the results that would have
+ // otherwise been empty (List or Doc)s will just not be written
+ list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
+ if (target.length || !excludeEmptyObjects) {
+ return target;
}
- });
- return optionsCollection;
+ };
+
+ const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
+ if (data === null || data === undefined) {
+ return undefined;
+ }
+ if (primitives.includes(typeof data)) {
+ return data;
+ }
+ if (typeof data === "object") {
+ return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
+ }
+ throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
+ };
}
+
}
Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index bb93de5ac..51a5768bf 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -4,11 +4,11 @@ import { ObjectField } from "./ObjectField";
import { Copy, ToScriptString, ToString } from "./FieldSymbols";
export enum InkTool {
- None,
- Pen,
- Highlighter,
- Eraser,
- Stamp
+ None = "none",
+ Pen = "pen",
+ Highlighter = "highlighter",
+ Eraser = "eraser",
+ Stamp = "stamp"
}
export interface PointData {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 503c60790..fc7f9ca80 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -65,14 +65,14 @@ export class ScriptField extends ObjectField {
@serializable(autoObject())
private captures?: ProxyField<Doc>;
- constructor(script: CompiledScript, setterscript?: CompileResult) {
+ constructor(script: CompiledScript, setterscript?: CompiledScript) {
super();
if (script?.options.capturedVariables) {
const doc = Doc.assign(new Doc, script.options.capturedVariables);
this.captures = new ProxyField(doc);
}
- this.setterscript = setterscript?.compiled ? setterscript : undefined;
+ this.setterscript = setterscript;
this.script = script;
}
@@ -98,10 +98,10 @@ export class ScriptField extends ObjectField {
// }
[Copy](): ObjectField {
- return new ScriptField(this.script);
+ return new ScriptField(this.script, this.setterscript);
}
toString() {
- return `${this.script.originalScript}`;
+ return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
}
[ToScriptString]() {
@@ -141,17 +141,16 @@ export class ComputedField extends ScriptField {
[Copy](): ObjectField {
- return new ComputedField(this.script);
+ return new ComputedField(this.script, this.setterscript);
}
public static MakeScript(script: string, params: object = {}) {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }, setterScript?: string) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
- const setCompiled = setterScript ? ScriptField.CompileScript(setterScript, params, true, capturedVariables) : undefined;
- return compiled.compiled ? new ComputedField(compiled, setCompiled?.compiled ? setCompiled : undefined) : undefined;
+ return compiled.compiled ? new ComputedField(compiled) : undefined;
}
public static MakeInterpolated(fieldKey: string, interpolatorKey: string) {
const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 142f7e079..6474ed148 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -12,6 +12,9 @@ export const documentSchema = createSchema({
links: listSpec(Doc), // computed (readonly) list of links associated with this document
// "Location" properties in a very general sense
+ currentFrame: "number", // current frame of a frame based collection (e.g., a progressive slide)
+ lastFrame: "number", // last frame of a frame based collection (e.g., a progressive slide)
+ activeFrame: "number", // the active frame of a frame based animated document
currentTimecode: "number", // current play back time of a temporal document (video / audio)
displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
@@ -19,10 +22,10 @@ export const documentSchema = createSchema({
y: "number", // y coordinate when in a freeform view
z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview
zIndex: "number", // zIndex of a document in a freeform view
- scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
- scrollX: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
- scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
- scrollLeft: "number", // scroll position of a scrollable document (pdf, text, web)
+ _scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+ _scrollX: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+ _scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
+ _scrollLeft: "number", // scroll position of a scrollable document (pdf, text, web)
// appearance properties on the layout
_autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents
@@ -60,6 +63,7 @@ export const documentSchema = createSchema({
letterSpacing: "string",
opacity: "number", // opacity of document
strokeWidth: "number",
+ strokeBezier: "number",
textTransform: "string",
treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden
treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
@@ -77,7 +81,7 @@ export const documentSchema = createSchema({
isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee)
lockedPosition: "boolean", // whether the document can be moved (dragged)
_lockedTransform: "boolean",// whether a freeformview can pan/zoom
-
+
// drag drop properties
dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a287b0210..54e7eca28 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym } from "./Doc";
+import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
@@ -106,6 +106,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe
"LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
+ if (target[AclSym]) return true;
if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
if (!prop.startsWith("_")) {
console.log(prop + " is deprecated - switch to _" + prop);
@@ -116,7 +117,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
return true;
}
}
- if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript) {
+ if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
}
return _setter(target, prop, value, receiver);
@@ -124,6 +125,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
let prop = in_prop;
+ if (in_prop === AclSym) return target[AclSym];
+ if (target[AclSym] === AclPrivate) return undefined;
if (prop === LayoutSym) {
return target.__LAYOUT__;
}
@@ -148,6 +151,9 @@ export function getter(target: any, in_prop: string | symbol | number, receiver:
function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any {
receiver = receiver || target[SelfProxy];
+ if (target === undefined) {
+ console.log("");
+ }
let field = target.__fields[prop];
for (const plugin of getterPlugins) {
const res = plugin(receiver, prop, field);
@@ -160,7 +166,7 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
}
if (field === undefined && !ignoreProto && prop !== "proto") {
const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
- if (proto instanceof Doc) {
+ if (proto instanceof Doc && proto[AclSym] !== AclPrivate) {
return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 231870531..b15042f9f 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -13,9 +13,7 @@ import { observable } from 'mobx';
import { Utils } from '../Utils';
import MobileInterface from './MobileInterface';
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-
-
-
+import { resolvedPorts } from '../client/views/Main';
// const onPointerDown = (e: React.TouchEvent) => {
// let imgInput = document.getElementById("input_image_file");
@@ -106,10 +104,10 @@ class Uploader extends React.Component {
}
-// DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
+// DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "image upload");
(async () => {
const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + "mobile");
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email + "mobile");
await Docs.Prototypes.initialize();
if (info.id !== "__guest__") {
// a guest will not have an id registered
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 6c2e797d6..da14ffc88 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -12,7 +12,6 @@ import { Scripting } from '../client/util/Scripting';
import { Transform } from '../client/util/Transform';
import { DocumentDecorations } from '../client/views/DocumentDecorations';
import GestureOverlay from '../client/views/GestureOverlay';
-import { InkingControl } from '../client/views/InkingControl';
import { DocumentView } from '../client/views/nodes/DocumentView';
import { RadialMenu } from '../client/views/nodes/RadialMenu';
import { PreviewCursor } from '../client/views/PreviewCursor';
@@ -26,6 +25,7 @@ import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
import "./MobileInterface.scss";
import { CollectionView } from '../client/views/collections/CollectionView';
+import { InkingStroke } from '../client/views/InkingStroke';
library.add(faLongArrowAltLeft);
@@ -68,7 +68,7 @@ export default class MobileInterface extends React.Component {
}
onSwitchInking = () => {
- InkingControl.Instance.switchTool(InkTool.Pen);
+ Doc.SetSelectedTool(InkTool.Pen);
MobileInterface.Instance.drawingInk = true;
DocServer.Mobile.dispatchOverlayTrigger({
@@ -134,7 +134,7 @@ export default class MobileInterface extends React.Component {
onBack = (e: React.MouseEvent) => {
this.switchCurrentView((userDoc: Doc) => this.mainDoc);
- InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+ Doc.SetSelectedTool(InkTool.None); // TODO: switch to previous tool
DocServer.Mobile.dispatchOverlayTrigger({
enableOverlay: false,
@@ -187,6 +187,7 @@ export default class MobileInterface extends React.Component {
Document={this.mainContainer}
DataDoc={undefined}
LibraryPath={emptyPath}
+ filterAddDocument={returnTrue}
fieldKey={""}
dropAction={"alias"}
bringToFront={emptyFunction}
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 3b6170f68..20e14a68d 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,12 +1,6 @@
-import { NDollarRecognizer } from "./ndollar";
-import { Type } from "typescript";
-import { InkField, PointData } from "../fields/InkField";
-import { Docs } from "../client/documents/Documents";
-import { Doc, WidthSym, HeightSym } from "../fields/Doc";
-import { NumCast } from "../fields/Types";
-import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
import { Rect } from "react-measure";
-import { Scripting } from "../client/util/Scripting";
+import { PointData } from "../fields/InkField";
+import { NDollarRecognizer } from "./ndollar";
export namespace GestureUtils {
export class GestureEvent {
@@ -39,7 +33,10 @@ export namespace GestureUtils {
EndBracket = "endbracket",
Stroke = "stroke",
Scribble = "scribble",
- Text = "text"
+ Text = "text",
+ Triangle = "triangle",
+ Circle = "circle",
+ Rectangle = "rectangle",
}
export const GestureRecognizer = new NDollarRecognizer(false);
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index e5740d105..9d42035d1 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -142,7 +142,7 @@ export class Result {
//
// NDollarRecognizer constants
//
-const NumMultistrokes = 4;
+const NumMultistrokes = 7;
const NumPoints = 96;
const SquareSize = 250.0;
const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35)
@@ -190,6 +190,21 @@ export class NDollarRecognizer {
// new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100))
));
+ this.Multistrokes[4] = new Multistroke(GestureUtils.Gestures.Triangle, useBoundedRotationInvariance, new Array(
+ new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))
+ ));
+ this.Multistrokes[5] = new Multistroke(GestureUtils.Gestures.Circle, useBoundedRotationInvariance, new Array(
+ new Array(new Point(200, 250), new Point(240, 230), new Point(248, 210), new Point(248, 190), new Point(240, 170), new Point(200, 150), new Point(160, 170), new Point(151, 190), new Point(151, 210), new Point(160, 230), new Point(201, 250))
+ ));
+ this.Multistrokes[6] = new Multistroke(GestureUtils.Gestures.Rectangle, useBoundedRotationInvariance, new Array(
+ new Array(
+ new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200),
+ new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230),
+ new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160),
+ new Point(106, 146), //new Point(80, 150), new Point(50, 146),
+ new Point(30, 143),
+ new Point(29, 220))
+ ));
//
// PREDEFINED STROKES
diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts
index e55850b29..684c00c0d 100644
--- a/src/scraping/buxton/final/BuxtonImporter.ts
+++ b/src/scraping/buxton/final/BuxtonImporter.ts
@@ -1,4 +1,4 @@
-import { readdirSync, writeFile, mkdirSync } from "fs";
+import { readdirSync, writeFile, mkdirSync, createReadStream, createWriteStream, existsSync, statSync } from "fs";
import * as path from "path";
import { red, cyan, yellow } from "colors";
import { Utils } from "../../../Utils";
@@ -9,6 +9,7 @@ const createImageSizeStream = require("image-size-stream");
import { parseXml } from "libxmljs";
import { strictEqual } from "assert";
import { Readable, PassThrough } from "stream";
+import { Directory, serverPathToFile, pathToDirectory } from "../../../server/ApiManagers/UploadManager";
/**
* This is an arbitrary bundle of data that gets populated
@@ -18,8 +19,7 @@ interface DocumentContents {
body: string;
imageData: ImageData[];
hyperlinks: string[];
- captions: string[];
- embeddedFileNames: string[];
+ tableData: TableData[];
longDescription: string;
}
@@ -40,6 +40,7 @@ export interface DeviceDocument {
secondaryKey: string;
attribute: string;
__images: ImageData[];
+ additionalMedia: ({ [type: string]: string } | undefined)[];
hyperlinks: string[];
captions: string[]; // from the table column
embeddedFileNames: string[]; // from the table column
@@ -255,6 +256,8 @@ const FormatMap = new Map<keyof DeviceDocument, ValueFormatDefinition<any>>([
]);
const sourceDir = path.resolve(__dirname, "source"); // where the Word documents are assumed to be stored
+const assetDir = path.resolve(__dirname, "assets"); // where any additional media content like pdfs will be stored. Each subdirectory of this
+// must follow the enum Directory.<type> naming scheme
const outDir = path.resolve(__dirname, "json"); // where the JSON output of these device documents will be written
const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton"); // where, in the server, these images will be written
const successOut = "buxton.json"; // the JSON list representing properly formatted documents
@@ -277,12 +280,13 @@ export default async function executeImport(emitter: ResultCallback, terminator:
rimraf.sync(dir);
mkdirSync(dir);
});
+ await transferAssets();
return parseFiles(wordDocuments, emitter, terminator);
} catch (e) {
const message = [
"Unable to find a source directory.",
- "Please ensure that the following directory exists and is populated with Word documents:",
- `${sourceDir}`
+ "Please ensure that the following directory exists:",
+ `${e.message}`
].join('\n');
console.log(red(message));
return { error: message };
@@ -290,6 +294,32 @@ export default async function executeImport(emitter: ResultCallback, terminator:
}
/**
+ * Builds a mirrored directory structure of all media / asset files
+ * within the server's public directory.
+ */
+async function transferAssets() {
+ for (const assetType of readdirSync(assetDir)) {
+ const subroot = path.resolve(assetDir, assetType);
+ if (!statSync(subroot).isDirectory()) {
+ continue;
+ }
+ const outputSubroot = serverPathToFile(assetType as Directory, "buxton");
+ if (existsSync(outputSubroot)) {
+ continue;
+ } else {
+ mkdirSync(outputSubroot);
+ }
+ for (const fileName of readdirSync(subroot)) {
+ const readStream = createReadStream(path.resolve(subroot, fileName));
+ const writeStream = createWriteStream(path.resolve(outputSubroot, fileName));
+ await new Promise<void>(resolve => {
+ readStream.pipe(writeStream).on("close", resolve);
+ });
+ }
+ }
+}
+
+/**
* Parse every Word document in the directory, notifying any callers as needed
* at each iteration via the emitter.
* @param wordDocuments the string list of Word document names to parse
@@ -356,6 +386,16 @@ const xPaths = {
hyperlinks: '//*[name()="Relationship" and contains(@Type, "hyperlink")]'
};
+interface TableData {
+ fileName: string;
+ caption: string;
+ additionalMedia?: { [type: string]: string };
+}
+
+const SuffixDirectoryMap = new Map<string, Directory>([
+ ["p", Directory.pdfs]
+]);
+
/**
* The meat of the script, images and text content are extracted here
* @param pathToDocument the path to the document relative to the root of the zip
@@ -370,8 +410,7 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
// get plain text
const body = document.root()?.text() ?? "No body found. Check the import script's XML parser.";
const captions: string[] = [];
- const embeddedFileNames: string[] = [];
-
+ const tableData: TableData[] = [];
// preserve paragraph formatting and line breaks that would otherwise get lost in the plain text parsing
// of the XML hierarchy
const paragraphs = document.find(xPaths.paragraphs).map(node => Utilities.correctSentences(node.text()).transformed!);
@@ -382,7 +421,7 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
// extract captions from the table cells
const tableRowsFlattened = document.find(xPaths.tableCells).map(node => node.text().trim());
const { length } = tableRowsFlattened;
- const numCols = 3;
+ const numCols = 4;
strictEqual(length > numCols, true, "No captions written."); // first row has the headers, not content
strictEqual(length % numCols === 0, true, "Improper caption formatting.");
@@ -392,8 +431,14 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
// have been added or reordered since this was written, but follow the same appraoch)
for (let i = numCols; i < tableRowsFlattened.length; i += numCols) {
const row = tableRowsFlattened.slice(i, i + numCols);
- embeddedFileNames.push(row[1]);
- captions.push(row[2]);
+ const entry: TableData = { fileName: row[1], caption: row[2] };
+ const key = SuffixDirectoryMap.get(row[3].toLowerCase());
+ if (key) {
+ const media: any = {};
+ media[key] = `${entry.fileName.split(".")[0]}.pdf`;
+ entry.additionalMedia = media;
+ }
+ tableData.push(entry);
}
// extract all hyperlinks embedded in the document
@@ -409,7 +454,7 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
// cleanup
zip.close();
- return { body, longDescription, imageData, captions, embeddedFileNames, hyperlinks };
+ return { body, longDescription, imageData, tableData, hyperlinks };
}
// zip relative path from root expression / filter used to isolate only media assets
@@ -492,11 +537,12 @@ async function writeImages(zip: any): Promise<ImageData[]> {
* @param contents the data already computed / parsed by extractFileContents
*/
function analyze(fileName: string, contents: DocumentContents): AnalysisResult {
- const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescription } = contents;
+ const { body, imageData, hyperlinks, tableData, longDescription } = contents;
const device: any = {
hyperlinks,
- captions,
- embeddedFileNames,
+ captions: tableData.map(({ caption }) => caption),
+ embeddedFileNames: tableData.map(({ fileName }) => fileName),
+ additionalMedia: tableData.map(({ additionalMedia }) => additionalMedia),
longDescription,
__images: imageData
};
diff --git a/src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf b/src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf
new file mode 100644
index 000000000..4746d2f41
--- /dev/null
+++ b/src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf
Binary files differ
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index 01d2dfcad..c5f3ca717 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -246,7 +246,7 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera
if (typeof result === "string") {
let path: string;
let matches: RegExpExecArray | null;
- if ((matches = /\:1050\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
+ if ((matches = /\:\d+\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
// image already exists on our server
path = serverPathToFile(Directory.images, matches[1]);
} else {
diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts
index 0136b758e..d2a9e9cce 100644
--- a/src/server/ApiManagers/PDFManager.ts
+++ b/src/server/ApiManagers/PDFManager.ts
@@ -7,54 +7,54 @@ import { createCanvas } from "canvas";
const imageSize = require("probe-image-size");
import * as express from "express";
import * as path from "path";
-import { Directory, serverPathToFile, clientPathToFile } from "./UploadManager";
+import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from "./UploadManager";
import { red } from "colors";
+import { resolve } from "path";
export default class PDFManager extends ApiManager {
protected initialize(register: Registration): void {
register({
- method: Method.GET,
- subscription: new RouteSubscriber("thumbnail").add("filename"),
- secureHandler: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res)
+ method: Method.POST,
+ subscription: new RouteSubscriber("thumbnail"),
+ secureHandler: async ({ req, res }) => {
+ const { coreFilename, pageNum, subtree } = req.body;
+ return getOrCreateThumbnail(coreFilename, pageNum, res, subtree);
+ }
});
}
}
-async function getOrCreateThumbnail(thumbnailName: string, res: express.Response): Promise<void> {
- const noExtension = thumbnailName.substring(0, thumbnailName.length - ".png".length);
- const pageString = noExtension.split('-')[1];
- const pageNumber = parseInt(pageString);
+async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string): Promise<void> {
+ const resolved = `${coreFilename}-${pageNum}.png`;
return new Promise<void>(async resolve => {
- const path = serverPathToFile(Directory.pdf_thumbnails, thumbnailName);
+ const path = serverPathToFile(Directory.pdf_thumbnails, resolved);
if (existsSync(path)) {
const existingThumbnail = createReadStream(path);
const { err, viewport } = await new Promise<any>(resolve => {
imageSize(existingThumbnail, (err: any, viewport: any) => resolve({ err, viewport }));
});
if (err) {
- console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`));
+ console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${resolved}:`));
console.log(err);
return;
}
- dispatchThumbnail(res, viewport, thumbnailName);
+ dispatchThumbnail(res, viewport, resolved);
} else {
- const offset = thumbnailName.length - pageString.length - 5;
- const name = thumbnailName.substring(0, offset) + ".pdf";
- const path = serverPathToFile(Directory.pdfs, name);
- await CreateThumbnail(path, pageNumber, res);
+ await CreateThumbnail(coreFilename, pageNum, res, subtree);
}
resolve();
});
}
-async function CreateThumbnail(file: string, pageNumber: number, res: express.Response) {
- const documentProxy = await Pdfjs.getDocument(file).promise;
+async function CreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string) {
+ const sourcePath = resolve(pathToDirectory(Directory.pdfs), `${subtree ?? ""}${coreFilename}.pdf`);
+ const documentProxy = await Pdfjs.getDocument(sourcePath).promise;
const factory = new NodeCanvasFactory();
- const page = await documentProxy.getPage(pageNumber);
+ const page = await documentProxy.getPage(pageNum);
const viewport = page.getViewport(1 as any);
const { canvas, context } = factory.create(viewport.width, viewport.height);
const renderContext = {
@@ -64,14 +64,13 @@ async function CreateThumbnail(file: string, pageNumber: number, res: express.Re
};
await page.render(renderContext).promise;
const pngStream = canvas.createPNGStream();
- const filenames = path.basename(file).split(".");
- const thumbnailName = `${filenames[0]}-${pageNumber}.png`;
- const pngFile = serverPathToFile(Directory.pdf_thumbnails, thumbnailName);
+ const resolved = `${coreFilename}-${pageNum}.png`;
+ const pngFile = serverPathToFile(Directory.pdf_thumbnails, resolved);
const out = createWriteStream(pngFile);
pngStream.pipe(out);
return new Promise<void>((resolve, reject) => {
out.on("finish", () => {
- dispatchThumbnail(res, viewport, thumbnailName);
+ dispatchThumbnail(res, viewport, resolved);
resolve();
});
out.on("error", error => {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index c887aa4e6..2bf4c1956 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -15,7 +15,9 @@ const parse = require('pdf-parse');
import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager';
import { red } from 'colors';
import { Stream } from 'stream';
+import { resolvedPorts } from './server_Initialization';
const requestImageSize = require("../client/util/request-image-size");
+import { resolvedServerUrl } from "./server_Initialization";
export enum SizeSuffix {
Small = "_s",
@@ -184,7 +186,7 @@ export namespace DashUploadUtils {
if (error !== null) {
return error;
}
- source = `http://localhost:1050${clientPathToFile(Directory.images, resolved)}`;
+ source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
}
let resolvedUrl: string;
/**
@@ -194,14 +196,14 @@ export namespace DashUploadUtils {
* basename subtree (i.e. /images/<some_guid>.<ext>) and put it on the end of the server's url.
*
* This can always be localhost, regardless of whether this is on the server or not, since we (the server, not the client)
- * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost:1050 refers to the same thing
- * as the full dash-release.eastus.cloudapp.azure.com:1050.
+ * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost:<port> refers to the same thing
+ * as the full dash-release.eastus.cloudapp.azure.com:<port>.
*/
const matches = isLocal().exec(source);
if (matches === null) {
resolvedUrl = source;
} else {
- resolvedUrl = `http://localhost:1050/${matches[1].split("\\").join("/")}`;
+ resolvedUrl = `${resolvedServerUrl}/${matches[1].split("\\").join("/")}`;
}
// See header comments: not all image files have exif data (I believe only JPG is the only format that can have it)
const exifData = await parseExifData(resolvedUrl);
diff --git a/src/server/index.ts b/src/server/index.ts
index ff74cfb33..590affd06 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -5,7 +5,7 @@ import * as path from 'path';
import { Database } from './database';
import { DashUploadUtils } from './DashUploadUtils';
import RouteSubscriber from './RouteSubscriber';
-import initializeServer from './server_Initialization';
+import initializeServer, { resolvedPorts } from './server_Initialization';
import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager';
import * as qs from 'query-string';
import UtilManager from './ApiManagers/UtilManager';
@@ -95,6 +95,11 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: ({ res }) => res.send(true)
});
+ addSupervisedRoute({
+ method: Method.GET,
+ subscription: "/resolvedPorts",
+ secureHandler: ({ res }) => res.send(resolvedPorts)
+ });
const serve: PublicHandler = ({ req, res }) => {
const detector = new mobileDetect(req.headers['user-agent'] || "");
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 91a3cb6bf..7178add93 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -1,4 +1,5 @@
import { Database } from "./database";
+import { resolvedPorts } from "./server_Initialization";
//npx ts-node src/server/remapUrl.ts
@@ -34,7 +35,7 @@ async function update() {
if (url.href.includes("localhost") && url.href.includes("Bill")) {
dynfield = true;
- update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:1050${url.pathname}` };
+ update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` };
}
}
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index d370385b2..744d4547b 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -20,8 +20,8 @@ import * as fs from 'fs';
import * as request from 'request';
import RouteSubscriber from './RouteSubscriber';
import { publicDirectory } from '.';
-import { logPort, pathFromRoot, } from './ActionUtilities';
-import { blue, yellow, red } from 'colors';
+import { logPort } from './ActionUtilities';
+import { blue, yellow } from 'colors';
import * as cors from "cors";
import { createServer, Server as HttpsServer } from "https";
import { Server as HttpServer } from "http";
@@ -32,6 +32,9 @@ import { SSL } from './apis/google/CredentialsLoader';
export type RouteSetter = (server: RouteManager) => void;
export let disconnect: Function;
+export let resolvedPorts: { server: number, socket: number } = { server: 1050, socket: 4321 };
+export let resolvedServerUrl: string;
+
export default async function InitializeServer(routeSetter: RouteSetter) {
const app = buildWithMiddleware(express());
@@ -55,17 +58,19 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
registerRelativePath(app);
let server: HttpServer | HttpsServer;
- const { serverPort } = process.env;
- const resolved = isRelease && serverPort ? Number(serverPort) : 1050;
+ const { serverPort, serverName } = process.env;
+ isRelease && serverPort && (resolvedPorts.server = Number(serverPort));
await new Promise<void>(resolve => server = isRelease ?
- createServer(SSL.Credentials, app).listen(resolved, resolve) :
- app.listen(resolved, resolve)
+ createServer(SSL.Credentials, app).listen(resolvedPorts.server, resolve) :
+ app.listen(resolvedPorts.server, resolve)
);
- logPort("server", resolved);
+ logPort("server", resolvedPorts.server);
+
+ resolvedServerUrl = `${isRelease && serverName ? `https://${serverName}.com` : "http://localhost"}:${resolvedPorts.server}`;
// initialize the web socket (bidirectional communication: if a user changes
// a field on one client, that change must be broadcast to all other clients)
- WebSocket.initialize(isRelease, app);
+ await WebSocket.initialize(isRelease, app);
disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
return isRelease;
@@ -174,11 +179,11 @@ function registerRelativePath(server: express.Express) {
server.use("*", (req, res) => {
const relativeUrl = req.originalUrl;
if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
- const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:1050/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
- const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:1050/corsProxy/ )
+ const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
+ const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart)
const absoluteTargetBaseUrl = actualReferUrl.match(/http[s]?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
- const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:1050/corsProxy/https://en.wikipedia.org/<somethingelse>)
+ const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
res.redirect(redirectedProxiedUrl);
} else if (relativeUrl.startsWith("/search")) { // detect search query and use default search engine
res.redirect(req.headers.referer + "corsProxy/" + encodeURIComponent("http://www.google.com" + relativeUrl));
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 21e772e83..d55c2e198 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -16,6 +16,7 @@ import executeImport from "../scraping/buxton/final/BuxtonImporter";
import { DocumentsCollection } from "./IDatabase";
import { createServer, Server } from "https";
import * as express from "express";
+import { resolvedPorts } from './server_Initialization';
export namespace WebSocket {
@@ -23,21 +24,21 @@ export namespace WebSocket {
const clients: { [key: string]: Client } = {};
export const socketMap = new Map<SocketIO.Socket, string>();
export let disconnect: Function;
- const defaultPort = 4321;
export async function initialize(isRelease: boolean, app: express.Express) {
let io: sio.Server;
- let resolved: number;
if (isRelease) {
const { socketPort } = process.env;
- resolved = socketPort ? Number(socketPort) : defaultPort;
+ if (socketPort) {
+ resolvedPorts.socket = Number(socketPort);
+ }
let socketEndpoint: Server;
- await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolved, resolve));
+ await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
io = sio(socketEndpoint!, SSL.Credentials as any);
} else {
- io = sio().listen(resolved = defaultPort);
+ io = sio().listen(resolvedPorts.socket);
}
- logPort("websocket", resolved);
+ logPort("websocket", resolvedPorts.socket);
console.log();
io.on("connection", function (socket: Socket) {
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 850c533fc..452882e09 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -5,6 +5,8 @@ declare module 'react-image-lightbox-with-rotate';
declare module 'cors';
declare module 'webrtc-adapter';
+declare module 'bezier-curve';
+declare module 'fit-curve'
declare module '@react-pdf/renderer' {