aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2020-06-08 14:18:11 -0400
committerBob Zeleznik <zzzman@gmail.com>2020-06-08 14:18:11 -0400
commit3dea269077151542bc2450bccd749ede87681556 (patch)
tree10b44861d966b2f996d00e5628d6a1fb048370df /src
parent632dec95b9fccccef13b50cb41fc598613a9df1e (diff)
a bunch of cleanup to fix import order and to organize/restructure ink things in the right places.
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx7
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts3
-rw-r--r--src/client/documents/Documents.ts495
-rw-r--r--src/client/util/CurrentUserUtils.ts8
-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.tsx23
-rw-r--r--src/client/views/AntimodeMenu.tsx1
-rw-r--r--src/client/views/DocComponent.tsx8
-rw-r--r--src/client/views/GestureOverlay.tsx83
-rw-r--r--src/client/views/InkingControl.tsx109
-rw-r--r--src/client/views/InkingStroke.tsx45
-rw-r--r--src/client/views/PreviewCursor.tsx4
-rw-r--r--src/client/views/Touchable.tsx2
-rw-r--r--src/client/views/collections/CollectionMapView.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx25
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx42
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx283
-rw-r--r--src/client/views/nodes/ColorBox.tsx50
-rw-r--r--src/client/views/nodes/DocumentView.tsx13
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx4
-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.tsx3
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx12
-rw-r--r--src/client/views/pdf/PDFViewer.tsx6
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx4
-rw-r--r--src/fields/Doc.ts322
-rw-r--r--src/pen-gestures/GestureUtils.ts10
33 files changed, 688 insertions, 924 deletions
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index ce7f49e64..a7045ccb7 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.selectedTool && !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 b816d1617..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";
@@ -195,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 71bf8a516..994f8a147 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 { 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,12 @@ import { ContextMenu } from "../views/ContextMenu";
import { LinkBox } from "../views/nodes/LinkBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ComparisonBox } from "../views/nodes/ComparisonBox";
+import { Id } from "../../fields/FieldSymbols";
+import { listSpec } from "../../fields/Schema";
+import { ObjectField } from "../../fields/ObjectField";
+import { RefField } from "../../fields/RefField";
+import { runInAction } from "mobx";
+import { UndoManager } from "../util/UndoManager";
const path = require('path');
export interface DocumentOptions {
@@ -455,7 +459,7 @@ export namespace Docs {
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);
@@ -777,225 +781,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, selectedBezier } = InkingControl.Instance;
- created = Docs.Create.InkDocument(selectedColor, selectedTool, selectedWidth, selectedBezier, (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 {
@@ -1049,6 +834,155 @@ 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(InkingStroke.InkColor, Doc.selectedTool, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, (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 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 => DocUtils.makeClone(d as Doc, cloneMap, rtfs)));
+ } else if (doc[key] instanceof Doc) {
+ copy[key] = key.includes("layout[") ? undefined : DocUtils.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] = DocUtils.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 = DocUtils.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 addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void {
ContextMenu.Instance.addItem({
description: "Add Note ...",
@@ -1083,6 +1017,119 @@ 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..d2614a898 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";
@@ -221,7 +221,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) {
@@ -612,7 +612,7 @@ 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)"),
+ onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)"),
author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activePen.inkPen, this)`), activePen: doc
});
}
@@ -692,6 +692,8 @@ export class CurrentUserUtils {
doc.title = Doc.CurrentUserEmail;
doc.activePen = doc;
doc.inkColor = StrCast(doc.backgroundColor, "rgb(0, 0, 0)");
+ doc.inkWidth = StrCast(doc.inkWidth, "1");
+ doc.inkBezier = StrCast(doc.inkBezier, "");
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/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 ab1ccb25a..3948611f0 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,7 +1,6 @@
import React = require("react");
import * as beziercurve from 'bezier-curve';
import * as fitCurve from 'fit-curve';
-import InkOptionsMenu from "../views/collections/collectionFreeForm/InkOptionsMenu";
export namespace InteractionUtils {
export const MOUSETYPE = "mouse";
@@ -90,24 +89,12 @@ export namespace InteractionUtils {
return myTouches;
}
- export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string, bezier: string) {
+ 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 = "";
- var shape = "";
- if (InkOptionsMenu.Instance._circle) {
- shape = "circle";
- } else if (InkOptionsMenu.Instance._rectangle) {
- shape = "rectangle";
- } else if (InkOptionsMenu.Instance._triangle) {
- shape = "triangle";
- } else if (InkOptionsMenu.Instance._arrow) {
- shape = "arrow";
- } else if (InkOptionsMenu.Instance._line) {
- shape = "line";
- }
- if (shape !== "") {
+ 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 - left},${pt.Y - top} `, "");
+ 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)
@@ -124,10 +111,10 @@ export namespace InteractionUtils {
newPts.push({ X: point[0], Y: point[1] });
}
}
- pts = newPts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
+ 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 - left},${pt.Y - top} `, "");
+ pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
}
return (
<polyline
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..c398b2633 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.selectedTool === 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.selectedTool !== 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/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 5714970c1..4ea75d7d7 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,38 +1,30 @@
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 { InkingControl } from "./InkingControl";
+import { DocumentView } from "./nodes/DocumentView";
import { RadialMenu } from "./nodes/RadialMenu";
-import { SelectionManager } from "../util/SelectionManager";
-import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
+import HorizontalPalette from "./Palette";
+import { Touchable } from "./Touchable";
+import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import { InkingStroke } from "./InkingStroke";
@observer
@@ -500,7 +492,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.selectedTool === InkTool.Highlighter || Doc.selectedTool === InkTool.Pen)) {
this._points.push({ X: e.clientX, Y: e.clientY });
e.stopPropagation();
e.preventDefault();
@@ -514,7 +506,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.selectedTool === InkTool.Highlighter || Doc.selectedTool === InkTool.Pen)) {
this._points.push({ X: e.clientX, Y: e.clientY });
e.stopPropagation();
e.preventDefault();
@@ -585,12 +577,11 @@ export default class GestureOverlay extends Touchable {
//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: InkingStroke.InkColor,
+ width: InkingStroke.InkWidth
});
}
@@ -633,21 +624,11 @@ export default class GestureOverlay extends Touchable {
}
}
//if any of the shape is activated in the InkOptionsMenu
- else if (InkOptionsMenu.Instance._circle || InkOptionsMenu.Instance._triangle || InkOptionsMenu.Instance._rectangle || InkOptionsMenu.Instance._line || InkOptionsMenu.Instance._arrow) {
- if (InkOptionsMenu.Instance._circle) {
- this.makePolygon("circle", false);
- } else if (InkOptionsMenu.Instance._triangle) {
- this.makePolygon("triangle", false);
- } else if (InkOptionsMenu.Instance._rectangle) {
- this.makePolygon("rectangle", false);
- } else if (InkOptionsMenu.Instance._line) {
- this.makePolygon("line", false);
- } else if (InkOptionsMenu.Instance._arrow) {
- this.makePolygon("arrow", false);
- }
+ else if (InkingStroke.InkShape) {
+ this.makePolygon(InkingStroke.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
- InkOptionsMenu.Instance.allFalse();
+ InkingStroke.InkShape = "";
}
// if we're not drawing in a toolglass try to recognize as gesture
else {
@@ -827,11 +808,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, InkingControl.Instance.selectedBezier)}
+ {InteractionUtils.CreatePolyline(l, b.left, b.top, InkingStroke.InkColor, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, 1, 1, InkingStroke.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, InkingControl.Instance.selectedBezier)}
+ {InteractionUtils.CreatePolyline(this._points, B.left, B.top, InkingStroke.InkColor, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, 1, 1, InkingStroke.InkShape)}
</svg>]
];
}
@@ -921,15 +902,15 @@ 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;
+ GestureOverlay.Instance.SavedColor = InkingStroke.InkColor;
+ InkingControl.Instance.switchColor(color);
+ GestureOverlay.Instance.SavedWidth = InkingStroke.InkWidth;
InkingControl.Instance.switchWidth(width);
});
});
Scripting.addGlobal(function resetPen() {
runInAction(() => {
- InkingControl.Instance.updateSelectedColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
+ InkingControl.Instance.switchColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
InkingControl.Instance.switchWidth(GestureOverlay.Instance.SavedWidth ?? "2");
});
});
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 83109db1c..349bc6ffc 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,22 +1,12 @@
-import { action, computed, observable } from "mobx";
-import { ColorState } from 'react-color';
+import { action, observable } from "mobx";
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";
-import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
+import { InkingStroke } from "./InkingStroke";
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"; }
- @computed private get _selectedBezier(): string { return FieldValue(StrCast(Doc.UserDoc().inkBezier)) ?? "2"; }
@observable public _open: boolean = false;
constructor() {
InkingControl.Instance = this;
@@ -26,110 +16,27 @@ export class InkingControl {
// 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();
- }
@action
- inkOptionsMenuChangeColor = (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: "",
- };
- this.switchColor(col);
- InkOptionsMenu.Instance._colorBt = false;
- }
-
- @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;
- InkOptionsMenu.Instance._color = StrCast(Doc.UserDoc().backgroundColor);
- 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;
+ CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkWidth = InkingStroke.InkWidth = width);
}
- InkOptionsMenu.Instance._widthBt = false;
}
@action
switchBezier = (bezier: string): void => {
- if (!isNaN(parseInt(bezier))) {
- Doc.UserDoc().inkBezier = bezier;
- }
+ CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkBezier = InkingStroke.InkBezierApprox = isNaN(parseInt(bezier)) ? "" : bezier);
}
@action
- inkOptionsMenuChangeBezier = (e: React.PointerEvent): void => {
- if (InkOptionsMenu.Instance._bezierBt === true) {
- Doc.UserDoc().inkBezier = "300";
- } else {
- Doc.UserDoc().inkBezier = "0";
- }
- }
-
- @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;
- }
-
- @computed
- get selectedBezier() {
- return this._selectedBezier;
+ switchColor(value: string) {
+ CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkColor = InkingStroke.InkColor = value);
}
}
-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);
- //setup InkOptionsMenu(change jumpto value if necessary.Currenlty hardcoded to 300,300)
- pen ? InkOptionsMenu.Instance.jumpTo(300, 300) : InkOptionsMenu.Instance.fadeOut(true);
- InkOptionsMenu.Instance.changeColor = InkingControl.Instance.inkOptionsMenuChangeColor;
- InkOptionsMenu.Instance.changeBezier = InkingControl.Instance.inkOptionsMenuChangeBezier;
- InkOptionsMenu.Instance.changeWidth = InkingControl.Instance.switchWidth;
- InkOptionsMenu.Instance._widthSelected = width;
- InkOptionsMenu.Instance._color = 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 activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.switchColor(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
+Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.switchColor(color); }); \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 3dc0a5b20..78d729eee 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,19 +1,19 @@
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
+import { observable, runInAction } 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";
library.add(faPaintBrush);
@@ -23,6 +23,22 @@ const InkDocument = makeInterface(documentSchema);
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
+ @observable public static InkColor: string;
+ @observable public static InkWidth: string;
+ @observable public static InkBezierApprox: string;
+ @observable public static InkShape: string;
+
+ constructor(props: any) {
+ super(props);
+ if (InkingStroke.InkBezierApprox === undefined) {
+ runInAction(() => {
+ InkingStroke.InkBezierApprox = "";
+ InkingStroke.InkWidth = "1";
+ InkingStroke.InkColor = "black";
+ InkingStroke.InkShape = "";
+ });
+ }
+ }
private analyzeStrokes = () => {
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
@@ -38,22 +54,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),
- StrCast(this.layoutDoc.strokeBezier, InkingControl.Instance.selectedBezier));
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, InkingStroke.InkColor || "black"),
+ StrCast(this.layoutDoc.strokeWidth, InkingStroke.InkWidth || "1"),
+ StrCast(this.layoutDoc.strokeBezier, InkingStroke.InkBezierApprox || ""), 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",
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index dd65681d4..33150ab7c 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import "./PreviewCursor.scss";
-import { Docs } from '../documents/Documents';
+import { Docs, DocUtils } from '../documents/Documents';
import { Doc } from '../../fields/Doc';
import { Transform } from "../util/Transform";
import { DocServer } from '../DocServer';
@@ -65,7 +65,7 @@ export class PreviewCursor extends React.Component<{}> {
count++;
if (doc instanceof Doc) {
i === 1 && (first = doc);
- const alias = Doc.MakeClone(doc);
+ const alias = DocUtils.MakeClone(doc);
const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx;
const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty;
alias.x = newPoint[0] + deltaX;
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/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/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 423eb1d90..b60419258 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;
@@ -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 b2e1c0f73..e891c4274 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -757,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 fecba32c5..873f61331 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -14,36 +14,16 @@ import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../fields/Ty
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { ScriptBox } from '../ScriptBox';
import { Touchable } from '../Touchable';
-import { CollectionCarouselView } from './CollectionCarouselView';
-import { CollectionDockingView } from "./CollectionDockingView";
-import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionLinearView } from './CollectionLinearView';
-import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionStackingView } from './CollectionStackingView';
-import { CollectionStaffView } from './CollectionStaffView';
-import { SubCollectionViewProps } from './CollectionSubView';
-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;
@@ -519,4 +499,24 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
{this.facetWidth() < 10 ? (null) : this.filterView}
</div>);
}
-} \ No newline at end of file
+}
+import { SubCollectionViewProps } from './CollectionSubView';
+import { CollectionCarouselView } from './CollectionCarouselView';
+import { CollectionDockingView } from "./CollectionDockingView";
+import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
+import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
+import { CollectionSchemaView } from "./CollectionSchemaView";
+import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionStaffView } from './CollectionStaffView';
+import { CollectionTimeView } from './CollectionTimeView';
+import { CollectionTreeView } from "./CollectionTreeView";
+import { CollectionMapView } from './CollectionMapView';
+import { CollectionPileView } from './CollectionPileView';
+import './CollectionView.scss';
+import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents'; \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fb7784b58..576c0c560 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -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 } from "../../InkingStroke";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -391,7 +390,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.selectedTool === InkTool.Highlighter || Doc.selectedTool === InkTool.Pen)) {
return;
}
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
@@ -408,7 +407,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.selectedTool === InkTool.None) {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
}
@@ -432,13 +431,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.selectedTool === InkTool.None) {
this._lastX = pt.pageX;
this._lastY = pt.pageY;
e.preventDefault();
@@ -458,7 +457,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, InkingControl.Instance.selectedBezier, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+ const inkDoc = Docs.Create.InkDocument(InkingStroke.InkColor, Doc.selectedTool, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -619,7 +618,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return;
}
if (!e.cancelBubble) {
- const selectedTool = InkingControl.Instance.selectedTool;
+ const selectedTool = Doc.selectedTool;
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
@@ -641,7 +640,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.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
e.preventDefault();
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 44488cbcf..b35c9682e 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -1,269 +1,117 @@
import React = require("react");
import AntimodeMenu from "../../AntimodeMenu";
import { observer } from "mobx-react";
-import { unimplementedFunction } from "../../../../Utils";
import { observable, action } from "mobx";
import "./InkOptionsMenu.scss";
-
+import { InkingStroke } from "../../InkingStroke";
+import { Scripting } from "../../../util/Scripting";
+import { InkTool } from "../../../../fields/InkField";
+import { InkingControl } from "../../InkingControl";
+import { StrCast } from "../../../../fields/Types";
+import { ColorState } from "react-color";
+import { ColorBox } from "../../nodes/ColorBox";
@observer
export default class InkOptionsMenu extends AntimodeMenu {
static Instance: InkOptionsMenu;
- public changeColor: (color: string) => void = unimplementedFunction;
- public changeBezier: (e: React.PointerEvent) => void = unimplementedFunction;
- public changeWidth: (color: string) => void = unimplementedFunction;
-
- private _palette: (string)[];
- private _width: (string)[];
-
-
- public _circle: boolean;
- public _triangle: boolean;
- public _rectangle: boolean;
- public _arrow: boolean;
- public _line: boolean;
- public _widthSelected: string;
-
- @observable public _circleBt: boolean;
- @observable public _triangleBt: boolean;
- @observable public _rectangleBt: boolean;
- @observable public _arrowBt: boolean;
- @observable public _lineBt: boolean;
- @observable public _colorBt: boolean;
- @observable public _color: string;
- @observable public _bezierBt: boolean;
- @observable public _widthBt: boolean;
+ 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;
-
- this._circle = false;
- this._triangle = false;
- this._rectangle = false;
- this._arrow = false;
- this._line = false;
- this._circleBt = false;
- this._triangleBt = false;
- this._rectangleBt = false;
- this._arrowBt = false;
- this._lineBt = false;
- this._colorBt = false;
- this._bezierBt = false;
- this._widthBt = false;
-
- this._color = "";
- this._widthSelected = "";
-
-
- this._palette = [
- "D0021B", "F5A623", "F8E71C", "8B572A", "7ED321", "417505", "9013FE", "4A90E2", "50E3C2", "B8E986", "000000", "4A4A4A", "9B9B9B", "FFFFFF",
- ];
-
- this._width = [
- "1", "5", "10", "100", "200", "300"
- ];
-
- }
-
-
-
- drag = (e: React.PointerEvent) => {
- this.dragStart(e);
+ this._canFade = false; // don't let the inking menu fade away
}
-
-
-
-
- @action
- toggleCircle = (e: React.PointerEvent) => {
- const curr = this._circle;
- this.allFalse();
- curr ? this._circle = false : this._circle = true;
- this._circleBt = this._circle;
- }
- @action
- toggleTriangle = (e: React.PointerEvent) => {
- const curr = this._triangle;
- this.allFalse();
- curr ? this._triangle = false : this._triangle = true;
- this._triangleBt = this._triangle;
- }
- @action
- toggleRectangle = (e: React.PointerEvent) => {
- const curr = this._rectangle;
- this.allFalse();
- curr ? this._rectangle = false : this._rectangle = true;
- this._rectangleBt = this._rectangle;
- }
- @action
- toggleArrow = (e: React.PointerEvent) => {
- const curr = this._arrow;
- this.allFalse();
- curr ? this._arrow = false : this._arrow = true;
- this._arrowBt = this._arrow;
- }
@action
- toggleLine = (e: React.PointerEvent) => {
- const curr = this._line;
- this.allFalse();
- curr ? this._line = false : this._line = true;
- this._lineBt = this._line;
+ 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: "",
+ };
+ ColorBox.switchColor(col);
}
@action
- changeBezierClick = (e: React.PointerEvent) => {
- const curr = this._bezierBt;
- this.allFalse();
- curr ? this._bezierBt = false : this._bezierBt = true;
- this.changeBezier(e);
- }
-
- @action
- changeWidthClick = (e: React.PointerEvent) => {
- this._widthBt ? this._widthBt = false : this._widthBt = true;
- }
- @action
- changeColorClick = (e: React.PointerEvent) => {
- this._colorBt ? this._colorBt = false : this._colorBt = true;
- }
-
- allFalse = () => {
- this._circle = false;
- this._triangle = false;
- this._rectangle = false;
- this._arrow = false;
- this._line = false;
- this._circleBt = false;
- this._triangleBt = false;
- this._rectangleBt = false;
- this._arrowBt = false;
- this._lineBt = false;
- this._bezierBt = false;
+ changeBezier = (e: React.PointerEvent): void => {
+ InkingControl.Instance.switchBezier(!InkingStroke.InkBezierApprox ? "300" : "");
}
render() {
- var widthPicker;
- if (this._widthBt) {
+ 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">
- <button
- className="antimodeMenu-button"
- key="width"
- onPointerDown={this.changeWidthClick}
- style={this._widthBt ? { backgroundColor: "121212" } : {}}>
- W
- </button>
+ {widthPicker}
{this._width.map(wid => {
return <button
className="antimodeMenu-button"
key={wid}
- onPointerDown={() => this.changeWidth(wid)}
- style={this._colorBt ? { backgroundColor: "121212" } : {}}>
+ onPointerDown={action(() => { InkingControl.Instance.switchWidth(wid); this._widthBtn = false; })}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
{wid}
</button>;
-
})}
</div>;
- } else {
- widthPicker = <button
- className="antimodeMenu-button"
- key="width"
- onPointerDown={this.changeWidthClick}
- style={this._widthBt ? { backgroundColor: "121212" } : {}}>
- W
- </button>;
}
- var colorPicker;
- if (this._colorBt) {
+ 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: InkingStroke.InkColor ?? "121212" }}></div>
+ </button>;
+ if (this._colorBtn) {
colorPicker = <div className="btn-group">
- <button
- className="antimodeMenu-button"
- key="color"
- onPointerDown={this.changeColorClick}
- style={this._colorBt ? { backgroundColor: "121212" } : {}}>
- <div className="color-preview" style={this._color === "" ? { backgroundColor: "121212" } : { backgroundColor: this._color }}></div>
- </button>
+ {colorPicker}
{this._palette.map(color => {
return <button
className="antimodeMenu-button"
key={color}
- onPointerDown={() => this.changeColor(color)}
- style={this._colorBt ? { backgroundColor: "121212" } : {}}>
+ onPointerDown={action(() => { this.changeColor(color); this._colorBtn = false; })}
+ style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
<div className="color-preview" style={{ backgroundColor: color }}></div>
</button>;
})}
</div>;
- } else {
- colorPicker = <button
- className="antimodeMenu-button"
- title="colorChanger"
- key="color"
- onPointerDown={this.changeColorClick}
- style={this._colorBt ? { backgroundColor: "121212" } : {}}>
- <div className="color-preview" style={this._color === "" ? { backgroundColor: "121212" } : { backgroundColor: this._color }}></div>
- </button>;
}
-
const buttons = [
- <button
- className="antimodeMenu-button"
+ <button className="antimodeMenu-button"
title="Drag"
key="drag"
- onPointerDown={this.drag}>
+ onPointerDown={e => this.dragStart(e)}>
✜
</button>,
- <button
- className="antimodeMenu-button"
- title="Draw Circle"
- key="circle"
- onPointerDown={this.toggleCircle}
- style={this._circleBt ? { backgroundColor: "121212" } : {}}>
- O
- </button>,
- <button
- className="antimodeMenu-button"
- title="Draw Traingle"
- key="triangle"
- onPointerDown={this.toggleTriangle}
- style={this._triangleBt ? { backgroundColor: "121212" } : {}}>
- ∆
- </button>,
- <button
- className="antimodeMenu-button"
- title="Draw Rectangle"
- key="rectangle"
- onPointerDown={this.toggleRectangle}
- style={this._rectangleBt ? { backgroundColor: "121212" } : {}}>
- ロ
- </button>,
- <button
- className="antimodeMenu-button"
- title="Draw Arrow"
- key="arrow"
- onPointerDown={this.toggleArrow}
- style={this._arrowBt ? { backgroundColor: "121212" } : {}}>
- ➜
- </button>,
- <button
- className="antimodeMenu-button"
- title="Draw Line"
- key="line"
- onPointerDown={this.toggleLine}
- style={this._lineBt ? { backgroundColor: "121212" } : {}}>
- –
- </button>,
+ <>
+ {this._buttons.map((btn, i) => <button
+ className="antimodeMenu-button"
+ title={`Draw ${btn}`}
+ key={btn}
+ onPointerDown={action(e => InkingStroke.InkShape = btn)}
+ style={btn === InkingStroke.InkShape ? { backgroundColor: "121212" } : {}}>
+ {this._icons[i]}
+ </button>)},
+ </>,
<button
className="antimodeMenu-button"
title="Bezier changer"
key="bezier"
- onPointerDown={this.changeBezierClick}
- style={this._bezierBt ? { backgroundColor: "121212" } : {}}>
+ onPointerDown={e => this.changeBezier(e)}
+ style={InkingStroke.InkBezierApprox ? { backgroundColor: "121212" } : {}}>
B
</button>,
widthPicker,
@@ -271,4 +119,15 @@ export default class InkOptionsMenu extends AntimodeMenu {
];
return this.getElement(buttons);
}
-} \ No newline at end of file
+}
+Scripting.addGlobal(function activatePen(pen: any) {
+ InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None);
+ if (pen) {
+ InkingControl.Instance.switchWidth(StrCast(pen.inkWidth, "1"));
+ InkingControl.Instance.switchColor(StrCast(pen.inkColor, "black"));
+ InkingControl.Instance.switchBezier(StrCast(pen.inkBezier, ""));
+ InkOptionsMenu.Instance.jumpTo(300, 300);
+ } else {
+ InkOptionsMenu.Instance.fadeOut(true);
+ }
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 2ddf2c74a..762c57ae9 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { observer } from "mobx-react";
-import { SketchPicker } from 'react-color';
+import { SketchPicker, ColorState } from 'react-color';
import { documentSchema } from "../../../fields/documentSchemas";
import { makeInterface } from "../../../fields/Schema";
import { StrCast } from "../../../fields/Types";
@@ -10,6 +10,12 @@ import { ViewBoxBaseComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
import "./ColorBox.scss";
import { FieldView, FieldViewProps } from './FieldView';
+import { InkingStroke } from "../InkingStroke";
+import { Doc } from "../../../fields/Doc";
+import { InkTool } from "../../../fields/InkField";
+import { undoBatch } from "../../util/UndoManager";
+import { action } from "mobx";
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
type ColorDocument = makeInterface<[typeof documentSchema]>;
const ColorDocument = makeInterface(documentSchema);
@@ -18,20 +24,52 @@ const ColorDocument = makeInterface(documentSchema);
export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument>(ColorDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+ static decimalToHexString(number: number) {
+ if (number < 0) {
+ number = 0xFFFFFFFF + number + 1;
+ }
+ return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
+ }
+
+ @undoBatch
+ @action
+ static switchColor(color: ColorState) {
+ Doc.UserDoc().backgroundColor = color.hex.startsWith("#") ?
+ color.hex + (color.rgb.a ? ColorBox.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex;
+ InkingStroke.InkColor = StrCast(Doc.UserDoc().backgroundColor);
+ CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkColor = color.hex);
+
+ if (Doc.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
+ }
+ }
+ });
+ }
+ }
+
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']}
+ <SketchPicker onChange={ColorBox.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,
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> {InkingControl.Instance.selectedBezier ?? 2}</div>
- <input type="range" value={InkingControl.Instance.selectedBezier ?? 2} defaultValue={2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchBezier(e.target.value)} />
+ <div> {InkingStroke.InkWidth ?? 2}</div>
+ <input type="range" value={InkingStroke.InkWidth ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchWidth(e.target.value)} />
+ <div> {InkingStroke.InkBezierApprox ?? 2}</div>
+ <input type="range" value={InkingStroke.InkBezierApprox ?? 2} defaultValue={2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchBezier(e.target.value)} />
<br />
<br />
</div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e245e045c..196e61a31 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -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";
@@ -330,7 +329,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");
@@ -513,7 +512,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.selectedTool === InkTool.Highlighter || Doc.selectedTool === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
// TODO: check here for panning/inking
@@ -544,7 +543,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.selectedTool === InkTool.Highlighter || Doc.selectedTool === 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)
}
@@ -728,7 +727,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" });
@@ -1091,14 +1090,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.selectedTool !== 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;
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/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 9f1e99c77..81669dc2a 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);
@@ -293,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.selectedTool === 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..1184f32f1 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.selectedTool || !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..8c8b7a622 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.selectedTool || !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.selectedTool], () => {
+ const interactive = Doc.selectedTool === 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 c4ab3c9e2..dcc87068b 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");
@@ -425,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.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "")}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{view}
</div>;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc131cd38..c0a8bec40 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" });
}
@@ -1213,7 +1211,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.selectedTool || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
} else if (FormattedTextBoxComment.textBox === this) {
@@ -1268,7 +1266,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.selectedTool !== InkTool.None ? "-inking" : "")}
style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5bad248be..c18f69e1d 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,7 +25,6 @@ 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";
@@ -583,7 +581,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]();
@@ -652,7 +650,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 || SnappingManager.GetIsDragging() ? "-inking" : ""}`} id="overlay"
+ return <div className={`pdfViewer-overlay${Doc.selectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} id="overlay"
style={{ transform: `scale(${this._zoomed})` }}>
<CollectionFreeFormView {...this.props}
LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 2ea011316..0b1e90b1d 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -4,10 +4,8 @@ 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';
@@ -73,7 +71,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.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6891bf652..d635836bb 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -6,20 +6,18 @@ import { DocumentType } from "../client/documents/DocumentTypes";
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 { intersectRect } from "../Utils";
+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 {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -134,6 +132,7 @@ function fetchProto(doc: Doc) {
@scriptingGlobal
@Deserializable("Doc", fetchProto).withFields(["id"])
export class Doc extends RefField {
+ @computed public static get selectedTool(): InkTool { return FieldValue(NumCast(Doc.UserDoc().inkTool)) ?? InkTool.None; }
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
const doc = new Proxy<this>(this, {
@@ -618,76 +617,6 @@ export namespace Doc {
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>;
@@ -998,120 +927,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/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index a50cca2b0..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 {