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