From 3dea269077151542bc2450bccd749ede87681556 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 8 Jun 2020 14:18:11 -0400 Subject: a bunch of cleanup to fix import order and to organize/restructure ink things in the right places. --- src/fields/Doc.ts | 322 +++++++++++++++++++++++------------------------------- 1 file changed, 139 insertions(+), 183 deletions(-) (limited to 'src/fields/Doc.ts') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6891bf652..d635836bb 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -6,20 +6,18 @@ import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; import { UndoManager } from "../client/util/UndoManager"; -import { intersectRect, Utils } from "../Utils"; -import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols"; +import { intersectRect } from "../Utils"; +import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; +import { InkTool } from "./InkField"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; import { FieldId, RefField } from "./RefField"; import { RichTextField } from "./RichTextField"; import { listSpec } from "./Schema"; -import { ComputedField, ScriptField } from "./ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types"; +import { ComputedField } from "./ScriptField"; +import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; -import { Docs, DocumentOptions } from "../client/documents/Documents"; -import { PdfField, VideoField, AudioField, ImageField } from "./URLField"; -import { LinkManager } from "../client/util/LinkManager"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -134,6 +132,7 @@ function fetchProto(doc: Doc) { @scriptingGlobal @Deserializable("Doc", fetchProto).withFields(["id"]) export class Doc extends RefField { + @computed public static get selectedTool(): InkTool { return FieldValue(NumCast(Doc.UserDoc().inkTool)) ?? InkTool.None; } constructor(id?: FieldId, forceSave?: boolean) { super(id); const doc = new Proxy(this, { @@ -618,76 +617,6 @@ export namespace Doc { return copy; } - export function MakeClone(doc: Doc): Doc { - const cloneMap = new Map(); - const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = Doc.makeClone(doc, cloneMap, rtfMap); - rtfMap.map(({ copy, key, field }) => { - const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { - const mapped = cloneMap.get(id); - return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; - }; - const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { - const mapped = cloneMap.get(id); - return href + (mapped ? mapped[Id] : id); - }; - const regex = `(${Utils.prepend("/doc/")})([^"]*)`; - const re = new RegExp(regex, "g"); - copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); - }); - return copy; - } - - export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { - if (Doc.IsBaseProto(doc)) return doc; - if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = new Doc(undefined, true); - cloneMap.set(doc[Id], copy); - if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); - const exclude = Cast(doc.excludeFields, listSpec("string"), []); - Object.keys(doc).forEach(key => { - if (exclude.includes(key)) return; - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); - const copyObjectField = (field: ObjectField) => { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); - } else if (doc[key] instanceof Doc) { - copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields - } else { - copy[key] = ObjectField.MakeCopy(field); - if (field instanceof RichTextField) { - if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { - rtfs.push({ copy, key, field }); - } - } - } - }; - if (key === "proto") { - if (doc[key] instanceof Doc) { - copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); - } - } else { - if (field instanceof RefField) { - copy[key] = field; - } else if (cfield instanceof ComputedField) { - copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - (key === "links" && field instanceof ObjectField) && copyObjectField(field); - } else if (field instanceof ObjectField) { - copyObjectField(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happend... - } else { - copy[key] = field; - } - } - }); - Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); - copy.cloneOf = doc; - cloneMap.set(doc[Id], copy); - return copy; - } export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc; export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt; @@ -998,120 +927,147 @@ export namespace Doc { return false; } - // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) - export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, 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; - 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 (_). otherwise, fallback to a general match on - !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, 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; - if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { - fieldTemplate = Docs.Create.TextDocument("", options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - } - const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); - docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); - } - export function makeCustomView(doc: Doc, custom: boolean, layout: string) { - Doc.setNativeView(doc); - if (custom) { - makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined); + export namespace Get { + + const primitives = ["string", "number", "boolean"]; + + export interface JsonConversionOpts { + data: any; + title?: string; + appendToExisting?: { targetDoc: Doc, fieldKey?: string }; + excludeEmptyObjects?: boolean; } - } - export function iconify(doc: Doc) { - const layoutKey = Cast(doc.layoutKey, "string", null); - Doc.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined); - if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); - } - export function pileup(docList: Doc[], x?: number, y?: number) { - let w = 0, h = 0; - runInAction(() => { - docList.forEach(d => { - Doc.iconify(d); - w = Math.max(d[WidthSym](), w); - h = Math.max(d[HeightSym](), h); - }); - h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable - docList.forEach((d, i) => { - d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2; - d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2; - d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - }); - }); - if (x !== undefined && y !== undefined) { - const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; - newCollection._width = newCollection._height = 110; - //newCollection.borderRounding = "40px"; - newCollection._jitterRotation = 10; - newCollection._backgroundColor = "gray"; - newCollection._overflow = "visible"; - return newCollection; + const defaultKey = "json"; + + /** + * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily + * deep levels of nesting, converts the data and structure into nested documents with the appropriate fields. + * + * After building a hierarchy within / below a top-level document, it then returns that top-level parent. + * + * If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the + * string is invalid JSON, so we should assume that the input is the result of a JSON.parse() + * call that returned a regular string value to be stored as a Field. + * + * If we've received something other than a string, since the caller might also pass in the results of a + * JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number. + * Anything else (like a function, etc. passed in naively as any) is meaningless for this operation. + * + * All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else, + * lacking the key value structure, gets stored as a field in a wrapper document. + * + * @param data for convenience and flexibility, either a valid JSON string to be parsed, + * or the result of any JSON.parse() call. + * @param title an optional title to give to the highest parent document in the hierarchy. + * If whether this function creates a new document or appendToExisting is specified and that document already has a title, + * because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title. + * @param appendToExisting **if specified**, there are two cases, both of which return the target document: + * + * 1) the json to be converted can be represented as a document, in which case the target document will act as the root + * of the tree and receive all the conversion results as new fields on itself + * 2) the json can't be represented as a document, in which case the function will assign the field-level conversion + * results to either the specified key on the target document, or to its "json" key by default. + * + * If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls) + * to act as the root of the tree. + * + * One might choose to specify this field if you want to write to a document returned from a Document.Create function call, + * say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created + * from a default call to new Doc. + * + * @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even + * if they contain no data. By default, empty objects and arrays are ignored. + */ + export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt { + 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; + 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 => { + const hasEntries = Object.keys(object).length; + if (hasEntries || !excludeEmptyObjects) { + const resolved = target ?? new Doc; + if (hasEntries) { + let result: Opt; + Object.keys(object).map(key => { + // if excludeEmptyObjects is true, any qualifying conversions from toField will + // be undefined, and thus the results that would have + // otherwise been empty (List or Doc)s will just not be written + if (result = toField(object[key], excludeEmptyObjects, key)) { + resolved[key] = result; + } + }); + } + title && (resolved.title = title); + return resolved; + } + }; - export async function addFieldEnumerations(doc: Opt, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) { - let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey); - if (!(optionsCollection instanceof Doc)) { - optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set` }, enumeratedFieldKey); - Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc); - } - const options = optionsCollection as Doc; - const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc); - const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`; - targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options })); - targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options })); - targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options })); - enumerations.map(enumeration => { - const found = DocListCast(options.data).find(d => d.title === enumeration.title); - if (found) { - found._backgroundColor = enumeration._backgroundColor || found._backgroundColor; - found._color = enumeration.color || found._color; - } else { - Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, enumeration)); + /** + * For each element in the list, recursively convert it to a document or other field + * and push the field to the list if it is not undefined + * @param list the list to convert + * @returns the list mapped from JSON to field values, where each mapping + * might involve arbitrary recursion (since toField might itself call convertList) + */ + const convertList = (list: Array, excludeEmptyObjects: boolean): Opt> => { + const target = new List(); + let result: Opt; + // if excludeEmptyObjects is true, any qualifying conversions from toField will + // be undefined, and thus the results that would have + // otherwise been empty (List or Doc)s will just not be written + list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result)); + if (target.length || !excludeEmptyObjects) { + return target; } - }); - return optionsCollection; + }; + + const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt => { + if (data === null || data === undefined) { + return undefined; + } + if (primitives.includes(typeof data)) { + return data; + } + if (typeof data === "object") { + return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined); + } + throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`); + }; } + } Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; }); -- cgit v1.2.3-70-g09d2 From 4cc9c6ae9a2735266994c62c01818de4bdc25ac6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 8 Jun 2020 20:45:38 -0400 Subject: final cleanup of ink related stuff so that things are more structured and we avoid input cycles --- src/Utils.ts | 13 ++ .../apis/google_docs/GooglePhotosClientUtils.ts | 4 +- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/documents/Documents.ts | 81 +------------ src/client/util/CurrentUserUtils.ts | 59 +++++----- src/client/util/LinkManager.ts | 10 +- src/client/views/DocComponent.tsx | 6 +- src/client/views/GestureOverlay.tsx | 35 +++--- src/client/views/GlobalKeyHandler.ts | 37 +++--- src/client/views/InkingControl.scss | 131 --------------------- src/client/views/InkingControl.tsx | 42 ------- src/client/views/InkingStroke.tsx | 47 ++++---- src/client/views/PreviewCursor.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 33 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +-- .../collectionFreeForm/InkOptionsMenu.tsx | 95 ++++++++------- src/client/views/nodes/ColorBox.tsx | 43 +++---- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/PresBox.tsx | 2 +- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 2 +- src/client/views/webcam/DashWebRTCVideo.tsx | 4 +- src/fields/Doc.ts | 79 ++++++++++++- src/fields/InkField.ts | 10 +- src/mobile/MobileInterface.tsx | 7 +- 28 files changed, 312 insertions(+), 470 deletions(-) delete mode 100644 src/client/views/InkingControl.scss delete mode 100644 src/client/views/InkingControl.tsx (limited to 'src/fields/Doc.ts') diff --git a/src/Utils.ts b/src/Utils.ts index ef5002bec..e527634fd 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -2,6 +2,7 @@ import v4 = require('uuid/v4'); import v5 = require("uuid/v5"); import { Socket, Room } from 'socket.io'; import { Message } from './server/Message'; +import { ColorState } from 'react-color'; export namespace Utils { export let DRAG_THRESHOLD = 4; @@ -75,6 +76,18 @@ export namespace Utils { document.body.removeChild(textArea); } + export function decimalToHexString(number: number) { + if (number < 0) { + number = 0xFFFFFFFF + number + 1; + } + return (number < 16 ? "0" : "") + number.toString(16).toUpperCase(); + } + + export function colorString(color: ColorState) { + return color.hex.startsWith("#") ? + color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex; + } + export function fromRGBAstr(rgba: string) { const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/); const r = rm ? Number(rm[1]) : 0; diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index fef71ffeb..a604c7de1 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -8,7 +8,7 @@ import { Cast, StrCast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/SharedTypes"; import { Utils } from "../../../Utils"; -import { Docs, DocumentOptions } from "../../documents/Documents"; +import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox"; import GoogleAuthenticationManager from "../GoogleAuthenticationManager"; @@ -332,7 +332,7 @@ export namespace GooglePhotos { const url = data.url.href; const target = Doc.MakeAlias(source); const description = parseDescription(target, descriptionKey); - await Doc.makeCustomViewClicked(target, Docs.Create.FreeformDocument); + await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument); media.push({ url, description }); } if (media.length) { diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index a7045ccb7..748d571c0 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -349,7 +349,7 @@ export class YoutubeBox extends React.Component { const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && !Doc.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && !Doc.GetSelectedTool() && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <>
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 994f8a147..9c807eff2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -37,7 +37,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { QueryBox } from "../views/nodes/QueryBox"; import { ColorBox } from "../views/nodes/ColorBox"; 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 { RichTextField } from "../../fields/RichTextField"; import { extname } from "path"; @@ -47,10 +47,6 @@ import { ContextMenu } from "../views/ContextMenu"; import { LinkBox } from "../views/nodes/LinkBox"; import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; -import { Id } from "../../fields/FieldSymbols"; -import { listSpec } from "../../fields/Schema"; -import { ObjectField } from "../../fields/ObjectField"; -import { RefField } from "../../fields/RefField"; import { runInAction } from "mobx"; import { UndoManager } from "../util/UndoManager"; const path = require('path'); @@ -149,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 @@ -855,7 +851,7 @@ export namespace DocUtils { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(InkingStroke.InkColor, Doc.selectedTool, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, (field).inkData, resolved); + 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); @@ -913,76 +909,6 @@ export namespace DocUtils { return ctor ? ctor(path, options) : undefined; } - export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { - if (Doc.IsBaseProto(doc)) return doc; - if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = new Doc(undefined, true); - cloneMap.set(doc[Id], copy); - if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); - const exclude = Cast(doc.excludeFields, listSpec("string"), []); - Object.keys(doc).forEach(key => { - if (exclude.includes(key)) return; - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); - const copyObjectField = (field: ObjectField) => { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => DocUtils.makeClone(d as Doc, cloneMap, rtfs))); - } else if (doc[key] instanceof Doc) { - copy[key] = key.includes("layout[") ? undefined : DocUtils.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields - } else { - copy[key] = ObjectField.MakeCopy(field); - if (field instanceof RichTextField) { - if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { - rtfs.push({ copy, key, field }); - } - } - } - }; - if (key === "proto") { - if (doc[key] instanceof Doc) { - copy[key] = DocUtils.makeClone(doc[key]!, cloneMap, rtfs); - } - } else { - if (field instanceof RefField) { - copy[key] = field; - } else if (cfield instanceof ComputedField) { - copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - (key === "links" && field instanceof ObjectField) && copyObjectField(field); - } else if (field instanceof ObjectField) { - copyObjectField(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happend... - } else { - copy[key] = field; - } - } - }); - Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); - copy.cloneOf = doc; - cloneMap.set(doc[Id], copy); - return copy; - } - export function MakeClone(doc: Doc): Doc { - const cloneMap = new Map(); - const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = DocUtils.makeClone(doc, cloneMap, rtfMap); - rtfMap.map(({ copy, key, field }) => { - const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { - const mapped = cloneMap.get(id); - return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; - }; - const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { - const mapped = cloneMap.get(id); - return href + (mapped ? mapped[Id] : id); - }; - const regex = `(${Utils.prepend("/doc/")})([^"]*)`; - const re = new RegExp(regex, "g"); - copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); - }); - return copy; - } - export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void { ContextMenu.Instance.addItem({ description: "Add Note ...", @@ -1107,7 +1033,6 @@ export namespace DocUtils { } } - export async function addFieldEnumerations(doc: Opt, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) { let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey); if (!(optionsCollection instanceof Doc)) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d2614a898..b0cea9947 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -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; @@ -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(), @@ -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(["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(["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(["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)"), - 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,12 +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.inkWidth = StrCast(doc.inkWidth, "1"); - doc.inkBezier = StrCast(doc.inkBezier, ""); + 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/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/DocComponent.tsx b/src/client/views/DocComponent.tsx index c398b2633..3af570f1e 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -57,7 +57,7 @@ export function ViewBoxBaseComponent

(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);// && !Doc.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; @@ -146,9 +146,9 @@ export function ViewBoxAnnotatableComponent

this.props.whenActiveChanged(this._isChildActive = isActive)); - active = (outsideReaction?: boolean) => ((Doc.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) => (Doc.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/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 4ea75d7d7..3384b5fce 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -18,19 +18,18 @@ import { LinkManager } from "../util/LinkManager"; import { Scripting } from "../util/Scripting"; import { Transform } from "../util/Transform"; import "./GestureOverlay.scss"; -import { InkingControl } from "./InkingControl"; +import { ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke"; import { DocumentView } from "./nodes/DocumentView"; import { RadialMenu } from "./nodes/RadialMenu"; import HorizontalPalette from "./Palette"; import { Touchable } from "./Touchable"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; -import { InkingStroke } from "./InkingStroke"; - @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; @@ -492,7 +491,7 @@ export default class GestureOverlay extends Touchable { @action onPointerDown = (e: React.PointerEvent) => { - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.selectedTool === InkTool.Highlighter || Doc.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(); @@ -506,7 +505,7 @@ export default class GestureOverlay extends Touchable { @action onPointerMove = (e: PointerEvent) => { - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.selectedTool === InkTool.Highlighter || Doc.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(); @@ -580,8 +579,8 @@ export default class GestureOverlay extends Touchable { DocServer.Mobile.dispatchGesturePoints({ points: this._points, bounds: B, - color: InkingStroke.InkColor, - width: InkingStroke.InkWidth + color: ActiveInkColor(), + width: ActiveInkWidth() }); } @@ -624,11 +623,11 @@ export default class GestureOverlay extends Touchable { } } //if any of the shape is activated in the InkOptionsMenu - else if (InkingStroke.InkShape) { - this.makePolygon(InkingStroke.InkShape, false); + else if (this.InkShape) { + this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - InkingStroke.InkShape = ""; + this.InkShape = ""; } // if we're not drawing in a toolglass try to recognize as gesture else { @@ -808,11 +807,11 @@ export default class GestureOverlay extends Touchable { [this._strokes.map(l => { const b = this.getBounds(l); return - {InteractionUtils.CreatePolyline(l, b.left, b.top, InkingStroke.InkColor, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, 1, 1, InkingStroke.InkShape)} + {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)} ; }), this._points.length <= 1 ? (null) : - {InteractionUtils.CreatePolyline(this._points, B.left, B.top, InkingStroke.InkColor, InkingStroke.InkWidth, InkingStroke.InkBezierApprox, 1, 1, InkingStroke.InkShape)} + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)} ] ]; } @@ -902,16 +901,16 @@ Scripting.addGlobal(function setToolglass(tool: any) { }); Scripting.addGlobal(function setPen(width: any, color: any) { runInAction(() => { - GestureOverlay.Instance.SavedColor = InkingStroke.InkColor; - InkingControl.Instance.switchColor(color); - GestureOverlay.Instance.SavedWidth = InkingStroke.InkWidth; - InkingControl.Instance.switchWidth(width); + GestureOverlay.Instance.SavedColor = ActiveInkColor(); + SetActiveInkColor(color); + GestureOverlay.Instance.SavedWidth = ActiveInkWidth(); + SetActiveInkWidth(width); }); }); Scripting.addGlobal(function resetPen() { runInAction(() => { - InkingControl.Instance.switchColor(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; @@ -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 349bc6ffc..000000000 --- a/src/client/views/InkingControl.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { action, observable } from "mobx"; -import { Doc } from "../../fields/Doc"; -import { InkTool } from "../../fields/InkField"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { Scripting } from "../util/Scripting"; -import { InkingStroke } from "./InkingStroke"; - -export class InkingControl { - @observable static Instance: InkingControl; - @observable public _open: boolean = false; - constructor() { - InkingControl.Instance = this; - } - - switchTool = action((tool: InkTool): void => { - // this._selectedTool = tool; - Doc.UserDoc().inkTool = tool; - }); - - @action - switchWidth = (width: string): void => { - if (!isNaN(parseInt(width))) { - CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkWidth = InkingStroke.InkWidth = width); - } - } - - @action - switchBezier = (bezier: string): void => { - CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkBezier = InkingStroke.InkBezierApprox = isNaN(parseInt(bezier)) ? "" : bezier); - } - - @action - switchColor(value: string) { - CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkColor = InkingStroke.InkColor = value); - } -} -Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.switchColor(color); }); -Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); }); -Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); }); -Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); }); -Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); }); -Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.switchColor(color); }); \ No newline at end of file diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 78d729eee..b545ede54 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,6 +1,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faPaintBrush } from "@fortawesome/free-solid-svg-icons"; -import { observable, runInAction } from "mobx"; +import { observable, runInAction, action } from "mobx"; import { observer } from "mobx-react"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; @@ -14,6 +14,8 @@ import { ViewBoxBaseComponent } from "./DocComponent"; import "./InkingStroke.scss"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import React = require("react"); +import { Scripting } from "../util/Scripting"; +import { Doc } from "../../fields/Doc"; library.add(faPaintBrush); @@ -23,22 +25,6 @@ const InkDocument = makeInterface(documentSchema); @observer export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } - @observable public static InkColor: string; - @observable public static InkWidth: string; - @observable public static InkBezierApprox: string; - @observable public static InkShape: string; - - constructor(props: any) { - super(props); - if (InkingStroke.InkBezierApprox === undefined) { - runInAction(() => { - InkingStroke.InkBezierApprox = ""; - InkingStroke.InkWidth = "1"; - InkingStroke.InkColor = "black"; - InkingStroke.InkShape = ""; - }); - } - } private analyzeStrokes = () => { const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; @@ -59,9 +45,9 @@ export class InkingStroke extends ViewBoxBaseComponent ); } -} \ 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/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 33150ab7c..dd65681d4 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import "./PreviewCursor.scss"; -import { Docs, DocUtils } from '../documents/Documents'; +import { Docs } from '../documents/Documents'; import { Doc } from '../../fields/Doc'; import { Transform } from "../util/Transform"; import { DocServer } from '../DocServer'; @@ -65,7 +65,7 @@ export class PreviewCursor extends React.Component<{}> { count++; if (doc instanceof Doc) { i === 1 && (first = doc); - const alias = DocUtils.MakeClone(doc); + const alias = Doc.MakeClone(doc); const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx; const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty; alias.x = newPoint[0] + deltaX; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 873f61331..8a73565c8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,34 +1,37 @@ 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, AclSym, AclAddonly, AclReadonly } 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 { DocumentType } from '../../documents/DocumentTypes'; 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'; import { Touchable } from '../Touchable'; -import { Id } from '../../../fields/FieldSymbols'; -import { listSpec } from '../../../fields/Schema'; -import { ScriptField, ComputedField } from '../../../fields/ScriptField'; -import { InteractionUtils } from '../../util/InteractionUtils'; -import { ObjectField } from '../../../fields/ObjectField'; +import './CollectionView.scss'; 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 { @@ -500,6 +503,12 @@ export class CollectionView extends Touchable); } } + + + +// to avoid an import cycle that will cause runtime errors, +// we import all of the bindings needed within methods after the +// class has been defined. import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionDockingView } from "./CollectionDockingView"; @@ -515,8 +524,6 @@ import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import { CollectionMapView } from './CollectionMapView'; import { CollectionPileView } from './CollectionPileView'; -import './CollectionView.scss'; import { CollectionViewBaseChrome } from './CollectionViewChromes'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 576c0c560..9c0e5e917 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -45,7 +45,7 @@ import React = require("react"); import { CollectionViewType } from "../CollectionView"; import { Timeline } from "../../animationtimeline/Timeline"; import { SnappingManager } from "../../../util/SnappingManager"; -import { InkingStroke } from "../../InkingStroke"; +import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -390,7 +390,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.selectedTool === InkTool.Highlighter || Doc.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; @@ -407,7 +407,7 @@ export class CollectionFreeFormView extends CollectionSubView { - InkingControl.Instance.switchBezier(!InkingStroke.InkBezierApprox ? "300" : ""); + SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : ""); } - render() { + @computed get widthPicker() { var widthPicker = ; })}

; } + return widthPicker; + } + @computed get colorPicker() { var colorPicker = ; if (this._colorBtn) { - colorPicker =
+ colorPicker =
{colorPicker} {this._palette.map(color => { return , - <> - {this._buttons.map((btn, i) => )}, - , - , - widthPicker, - colorPicker, + title={`Draw ${btn}`} + key={btn} + onPointerDown={action(e => GestureOverlay.Instance.InkShape = btn)} + style={{ backgroundColor: btn === GestureOverlay.Instance.InkShape ? "121212" : "" }}> + {this._icons[i]} + )}, + ; + } + + @computed get bezierButton() { + return ; + } + + render() { + const buttons = [ + , + this.shapeButtons, + this.bezierButton, + this.widthPicker, + this.colorPicker, ]; return this.getElement(buttons); } } -Scripting.addGlobal(function activatePen(pen: any) { - InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); - if (pen) { - InkingControl.Instance.switchWidth(StrCast(pen.inkWidth, "1")); - InkingControl.Instance.switchColor(StrCast(pen.inkColor, "black")); - InkingControl.Instance.switchBezier(StrCast(pen.inkBezier, "")); +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/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 762c57ae9..0d6258cf3 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -1,20 +1,19 @@ import React = require("react"); +import { action } from "mobx"; import { observer } from "mobx-react"; -import { SketchPicker, ColorState } 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 { InkingStroke } from "../InkingStroke"; -import { Doc } from "../../../fields/Doc"; -import { InkTool } from "../../../fields/InkField"; -import { undoBatch } from "../../util/UndoManager"; -import { action } from "mobx"; import { FormattedTextBox } from "./formattedText/FormattedTextBox"; type ColorDocument = makeInterface<[typeof documentSchema]>; @@ -24,22 +23,13 @@ const ColorDocument = makeInterface(documentSchema); export class ColorBox extends ViewBoxBaseComponent(ColorDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); } - static decimalToHexString(number: number) { - if (number < 0) { - number = 0xFFFFFFFF + number + 1; - } - return (number < 16 ? "0" : "") + number.toString(16).toUpperCase(); - } - @undoBatch @action static switchColor(color: ColorState) { - Doc.UserDoc().backgroundColor = color.hex.startsWith("#") ? - color.hex + (color.rgb.a ? ColorBox.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex; - InkingStroke.InkColor = StrCast(Doc.UserDoc().backgroundColor); - CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.inkColor = color.hex); + Doc.UserDoc().backgroundColor = Utils.colorString(color); + SetActiveInkColor(color.hex); - if (Doc.selectedTool === InkTool.None) { + if (Doc.GetSelectedTool() === InkTool.None) { const selected = SelectionManager.SelectedDocuments(); selected.map(view => { const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory : @@ -56,6 +46,9 @@ export class ColorBox extends ViewBoxBaseComponent
-
{InkingStroke.InkWidth ?? 2}
- ) => InkingControl.Instance.switchWidth(e.target.value)} /> -
{InkingStroke.InkBezierApprox ?? 2}
- ) => InkingControl.Instance.switchBezier(e.target.value)} /> +
{ActiveInkWidth() ?? 2}
+ ) => SetActiveInkWidth(e.target.value)} /> +
{ActiveInkBezierApprox() ?? 2}
+ ) => SetActiveBezierApprox(e.target.value)} />

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 196e61a31..98be1adc0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -512,7 +512,7 @@ export class DocumentView extends DocComponent(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) || Doc.selectedTool === InkTool.Highlighter || Doc.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 @@ -543,7 +543,7 @@ export class DocumentView extends DocComponent(Docu onPointerMove = (e: PointerEvent): void => { if ((e as any).formattedHandled) { e.stopPropagation(); return; } - if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.selectedTool === InkTool.Highlighter || Doc.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) } @@ -1090,7 +1090,7 @@ export class DocumentView extends DocComponent(Docu @computed get ignorePointerEvents() { return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) || - (this.Document.type === DocumentType.INK && Doc.selectedTool !== InkTool.None); + (this.Document.type === DocumentType.INK && Doc.GetSelectedTool() !== InkTool.None); } @undoBatch @action diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 81669dc2a..dbc879920 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -293,7 +293,7 @@ export class PresBox extends ViewBoxBaseComponent 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) => ((Doc.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 1184f32f1..d75b864cf 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -132,7 +132,7 @@ export class ScreenshotBox extends ViewBoxBaseComponentLoading
:
{!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
: -
(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
; const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && !Doc.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); - + const classname = "webBox-cont" + (this.props.isSelected() && !Doc.GetSelectedTool() && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index d635836bb..981025483 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -6,7 +6,7 @@ import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; import { UndoManager } from "../client/util/UndoManager"; -import { intersectRect } from "../Utils"; +import { intersectRect, Utils } from "../Utils"; import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; import { InkTool } from "./InkField"; import { List } from "./List"; @@ -18,6 +18,7 @@ import { listSpec } from "./Schema"; import { ComputedField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; +import { LinkManager } from "../client/util/LinkManager"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -132,7 +133,6 @@ function fetchProto(doc: Doc) { @scriptingGlobal @Deserializable("Doc", fetchProto).withFields(["id"]) export class Doc extends RefField { - @computed public static get selectedTool(): InkTool { return FieldValue(NumCast(Doc.UserDoc().inkTool)) ?? InkTool.None; } constructor(id?: FieldId, forceSave?: boolean) { super(id); const doc = new Proxy(this, { @@ -486,6 +486,78 @@ export namespace Doc { return alias; } + + + export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { + if (Doc.IsBaseProto(doc)) return doc; + if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; + const copy = new Doc(undefined, true); + cloneMap.set(doc[Id], copy); + if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); + const exclude = Cast(doc.excludeFields, listSpec("string"), []); + Object.keys(doc).forEach(key => { + if (exclude.includes(key)) return; + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = ProxyField.WithoutProxy(() => doc[key]); + const copyObjectField = (field: ObjectField) => { + const list = Cast(doc[key], listSpec(Doc)); + if (list !== undefined && !(list instanceof Promise)) { + copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); + } else if (doc[key] instanceof Doc) { + copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields + } else { + copy[key] = ObjectField.MakeCopy(field); + if (field instanceof RichTextField) { + if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { + rtfs.push({ copy, key, field }); + } + } + } + }; + if (key === "proto") { + if (doc[key] instanceof Doc) { + copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); + } + } else { + if (field instanceof RefField) { + copy[key] = field; + } else if (cfield instanceof ComputedField) { + copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); + (key === "links" && field instanceof ObjectField) && copyObjectField(field); + } else if (field instanceof ObjectField) { + copyObjectField(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } else { + copy[key] = field; + } + } + }); + Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); + copy.cloneOf = doc; + cloneMap.set(doc[Id], copy); + return copy; + } + export function MakeClone(doc: Doc): Doc { + const cloneMap = new Map(); + const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; + const copy = Doc.makeClone(doc, cloneMap, rtfMap); + rtfMap.map(({ copy, key, field }) => { + const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; + }; + const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return href + (mapped ? mapped[Id] : id); + }; + const regex = `(${Utils.prepend("/doc/")})([^"]*)`; + const re = new RegExp(regex, "g"); + copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); + }); + return copy; + } + // // Determines whether the layout needs to be expanded (as a template). // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template @@ -743,6 +815,9 @@ export namespace Doc { export function SearchQuery(): string { return manager._searchQuery; } export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } export function UserDoc(): Doc { return manager._user_doc; } + + export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } + export function GetSelectedTool(): InkTool { return (FieldValue(StrCast(Doc.UserDoc().activeInkTool)) ?? InkTool.None) as InkTool; } export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } export function IsBrushed(doc: Doc) { return computedFn(function IsBrushed(doc: Doc) { diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index bb93de5ac..51a5768bf 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -4,11 +4,11 @@ import { ObjectField } from "./ObjectField"; import { Copy, ToScriptString, ToString } from "./FieldSymbols"; export enum InkTool { - None, - Pen, - Highlighter, - Eraser, - Stamp + None = "none", + Pen = "pen", + Highlighter = "highlighter", + Eraser = "eraser", + Stamp = "stamp" } export interface PointData { diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 6c2e797d6..da14ffc88 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -12,7 +12,6 @@ import { Scripting } from '../client/util/Scripting'; import { Transform } from '../client/util/Transform'; import { DocumentDecorations } from '../client/views/DocumentDecorations'; import GestureOverlay from '../client/views/GestureOverlay'; -import { InkingControl } from '../client/views/InkingControl'; import { DocumentView } from '../client/views/nodes/DocumentView'; import { RadialMenu } from '../client/views/nodes/RadialMenu'; import { PreviewCursor } from '../client/views/PreviewCursor'; @@ -26,6 +25,7 @@ import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from '../Utils'; import "./MobileInterface.scss"; import { CollectionView } from '../client/views/collections/CollectionView'; +import { InkingStroke } from '../client/views/InkingStroke'; library.add(faLongArrowAltLeft); @@ -68,7 +68,7 @@ export default class MobileInterface extends React.Component { } onSwitchInking = () => { - InkingControl.Instance.switchTool(InkTool.Pen); + Doc.SetSelectedTool(InkTool.Pen); MobileInterface.Instance.drawingInk = true; DocServer.Mobile.dispatchOverlayTrigger({ @@ -134,7 +134,7 @@ export default class MobileInterface extends React.Component { onBack = (e: React.MouseEvent) => { this.switchCurrentView((userDoc: Doc) => this.mainDoc); - InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool + Doc.SetSelectedTool(InkTool.None); // TODO: switch to previous tool DocServer.Mobile.dispatchOverlayTrigger({ enableOverlay: false, @@ -187,6 +187,7 @@ export default class MobileInterface extends React.Component { Document={this.mainContainer} DataDoc={undefined} LibraryPath={emptyPath} + filterAddDocument={returnTrue} fieldKey={""} dropAction={"alias"} bringToFront={emptyFunction} -- cgit v1.2.3-70-g09d2