From f4830de4f8c4794ec98e54be9ba8730e46155c35 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Mon, 6 Jul 2020 18:18:17 +0530 Subject: trying first implementation of storing acls --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fc63dfbf5..a0dbcd980 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -226,7 +226,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - if (!this.dataDoc[AclSym]) { + // if (!this.dataDoc[AclSym]) { // what? + console.log("here"); + if (getEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); -- cgit v1.2.3-70-g09d2 From 12285b8c0aff514a2345874508c35b6de1026ef4 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 9 Jul 2020 12:25:46 +0530 Subject: acls now work I think + some cleanup --- src/client/DocServer.ts | 1 - src/client/util/GroupManager.tsx | 13 +++- src/client/util/GroupMemberView.tsx | 2 +- src/client/util/SharingManager.tsx | 2 - src/client/views/DocComponent.tsx | 1 - src/client/views/GlobalKeyHandler.ts | 2 + src/client/views/collections/CollectionView.tsx | 21 +++--- src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 22 +++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/fields/Doc.ts | 24 +------ src/fields/util.ts | 84 +++++++++++----------- 12 files changed, 84 insertions(+), 90 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 860a8fd92..bac324c77 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -40,7 +40,6 @@ export namespace DocServer { export var PlaygroundFields: string[]; export function setPlaygroundFields(livePlaygroundFields: string[]) { - console.log("here"); DocServer.PlaygroundFields = livePlaygroundFields; livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground)); } diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 83b206f94..23bdd248b 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -14,6 +14,7 @@ import Select from 'react-select'; import "./GroupManager.scss"; import { StrCast } from "../../fields/Types"; import GroupMemberView from "./GroupMemberView"; +import { setGroups } from "../../fields/util"; library.add(fa.faWindowClose); @@ -54,7 +55,7 @@ export default class GroupManager extends React.Component<{}> { if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); }); }) - .finally(() => console.log(this.currentUserGroups)); + .finally(() => setGroups(this.currentUserGroups)); // (this.GroupManagerDoc?.data as List).forEach(group => { // Promise.resolve(group).then(resolvedGroup => { @@ -178,6 +179,10 @@ export default class GroupManager extends React.Component<{}> { groupDoc.groupName = groupName; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); + if (memberEmails.includes(Doc.CurrentUserEmail)) { + this.currentUserGroups.push(groupName); + setGroups(this.currentUserGroups); + } this.addGroup(groupDoc); } @@ -204,6 +209,12 @@ export default class GroupManager extends React.Component<{}> { // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared"); Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); SharingManager.Instance.removeGroup(group); + const members: string[] = JSON.parse(StrCast(group.members)); + if (members.includes(Doc.CurrentUserEmail)) { + const index = this.currentUserGroups.findIndex(groupName => groupName === group.groupName); + index !== -1 && this.currentUserGroups.splice(index, 1); + setGroups(this.currentUserGroups); + } if (group === this.currentGroup) { runInAction(() => this.currentGroup = undefined); } diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index cc279b6b2..742caa676 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -51,7 +51,7 @@ export default class GroupMemberView extends React.Component - + : null} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index bec6b973b..d64302456 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -359,8 +359,6 @@ export default class SharingManager extends React.Component<{}> { share = () => { this.selectedUsers?.forEach(user => { if (user.value.includes(indType)) { - console.log(user); - console.log(this.users.find(u => u.user.email === user.label)); this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); } else { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index e8c34d931..781673e59 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -138,7 +138,6 @@ export function ViewBoxAnnotatableComponent

!docList.includes(d)); - console.log("here"); const effectiveAcl = getEffectiveAcl(this.dataDoc); if (added.length) { if (effectiveAcl === AclReadonly) { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index a3a023164..45d53a5f5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -22,6 +22,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { DocumentLinksButton } from "./nodes/DocumentLinksButton"; import PDFMenu from "./pdf/PDFMenu"; import { ContextMenu } from "./ContextMenu"; +import GroupManager from "../util/GroupManager"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -107,6 +108,7 @@ export default class KeyManager { GoogleAuthenticationManager.Instance.cancel(); HypothesisAuthenticationManager.Instance.cancel(); SharingManager.Instance.close(); + GroupManager.Instance.close(); break; case "delete": case "backspace": diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 31f0c1df3..6a6a475c8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -8,7 +8,7 @@ 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 { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; +import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; @@ -132,8 +132,7 @@ export class CollectionView extends Touchable !docList.includes(d)); - console.log("here"); - const effectiveAcl = getEffectiveAcl(this.dataDoc); + const effectiveAcl = getEffectiveAcl(this.props.Document); if (added.length) { if (effectiveAcl === AclReadonly) { return false; @@ -167,13 +166,15 @@ export class CollectionView extends Touchable { - const docs = doc instanceof Doc ? [doc] : doc as Doc[]; - const targetDataDoc = this.props.Document[DataSym]; - const value = DocListCast(targetDataDoc[this.props.fieldKey]); - const result = value.filter(v => !docs.includes(v)); - if (result.length !== value.length) { - targetDataDoc[this.props.fieldKey] = new List(result); - return true; + if (getEffectiveAcl(this.props.Document) === AclEdit) { + const docs = doc instanceof Doc ? [doc] : doc as Doc[]; + const targetDataDoc = this.props.Document[DataSym]; + const value = DocListCast(targetDataDoc[this.props.fieldKey]); + const result = value.filter(v => !docs.includes(v)); + if (result.length !== value.length) { + targetDataDoc[this.props.fieldKey] = new List(result); + return true; + } } return false; } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d480c76d0..e34ceb994 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -184,7 +184,6 @@ export class DocumentContentsView extends React.Component 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns - console.log("here"); return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : (Docu @undoBatch @action - setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => { + setAcl = (acl: SharingPermissions) => { this.dataDoc.ACL = this.props.Document.ACL = acl; DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { if (d.author === Doc.CurrentUserEmail) d.ACL = acl; @@ -735,7 +735,7 @@ export class DocumentView extends DocComponent(Docu } @undoBatch @action - testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => { + testAcl = (acl: SharingPermissions) => { this.dataDoc.author = this.props.Document.author = "ADMIN"; this.dataDoc.ACL = this.props.Document.ACL = acl; DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { @@ -845,12 +845,12 @@ export class DocumentView extends DocComponent(Docu const existingAcls = cm.findByDescription("Privacy..."); const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; - aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" }); + aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" }); + aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" }); + aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" }); + aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" }); + aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" }); + aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" }); !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" }); // const recommender_subitems: ContextMenuProps[] = []; @@ -1206,9 +1206,9 @@ export class DocumentView extends DocComponent(Docu } render() { - console.log("here"); - if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null); if (!(this.props.Document instanceof Doc)) return (null); + if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null); + if (this.props.Document.hidden) return (null); const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || 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))); const finalOpacity = this.props.opacity ? this.props.opacity() : opacity; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a0dbcd980..ccf83cbf9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -227,7 +227,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); // if (!this.dataDoc[AclSym]) { // what? - console.log("here"); if (getEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c965dc282..27eabf451 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -109,7 +109,6 @@ const AclMap = new Map([ ]); export function fetchProto(doc: Doc) { - console.log("in fetchproto"); if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions const permissions: { [key: string]: symbol } = {}; @@ -118,22 +117,8 @@ export function fetchProto(doc: Doc) { if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!; }); - doc[AclSym] = permissions; - // const acl = Doc.Get(doc, "ACL", true); - // switch (acl) { - // case "ownerOnly": - // doc[AclSym] = AclPrivate; - // return undefined; - // case "readOnly": - // doc[AclSym] = AclReadonly; - // break; - // case "addOnly": - // doc[AclSym] = AclAddonly; - // break; - // // case "edit": - // // doc[AclSym] = AclEdit; - // } + if (Object.keys(permissions).length) doc[AclSym] = permissions; } if (doc.proto instanceof Promise) { @@ -208,7 +193,7 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; - public [AclSym]: any; + public [AclSym]: { [key: string]: symbol }; public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } @@ -232,8 +217,8 @@ export class Doc extends RefField { return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc; } return undefined; - } + } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; public static CurrentUserEmail: string = ""; @@ -840,7 +825,6 @@ export namespace Doc { } // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) export function IsBrushedDegreeUnmemoized(doc: Doc) { - console.log("here"); if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0; return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; } @@ -850,7 +834,6 @@ export namespace Doc { })(doc); } export function BrushDoc(doc: Doc) { - console.log("here"); if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetProto(doc), true); @@ -888,7 +871,6 @@ export namespace Doc { } const highlightManager = new HighlightBrush(); export function IsHighlighted(doc: Doc) { - console.log("here"); if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false; return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); } diff --git a/src/fields/util.ts b/src/fields/util.ts index a3e7a36f8..a1af1d3c5 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate, AclEdit, AclReadonly, AclAddonly } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; @@ -9,7 +9,6 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; import { ComputedField } from "./ScriptField"; import { ScriptCast } from "./Types"; -import GroupManager from "../client/util/GroupManager"; function _readOnlySetter(): never { @@ -108,57 +107,65 @@ export function OVERRIDE_ACL(val: boolean) { _overrideAcl = val; } -const HierarchyMapping = new Map([ - [AclPrivate, 0], - [AclReadonly, 1], - [AclAddonly, 2], - [AclEdit, 3] -]); +let currentUserGroups: string[] = []; +let currentUserEmail: string;// = Doc.CurrentUserEmail; -export function getEffectiveAcl(target: any): symbol { +export function setGroups(groups: string[]) { + currentUserGroups = groups; + currentUserEmail = Doc.CurrentUserEmail; +} - console.log("in getEffectiveAcl"); - if (target[AclSym].ACL) return target[AclSym].ACL; +export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { - let effectiveAcl = AclEdit; + const HierarchyMapping = new Map([ + [AclPrivate, 0], + [AclReadonly, 1], + [AclAddonly, 2], + [AclEdit, 3] + ]); - for (const [key, value] of Object.entries(target[AclSym])) { - if (key.startsWith("ACL-")) { - if (GroupManager.Instance.currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) { - if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { - effectiveAcl = value as symbol; - if (effectiveAcl === AclEdit) break; - } - } - } + if (!target[AclSym] && target instanceof Doc) { + fetchProto(target); } - return effectiveAcl; -} + if (target[AclSym] && Object.keys(target[AclSym]).length) { + + if (target.author === currentUserEmail) return AclEdit; + + if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; -function testPermission(target: any, in_prop: string | symbol | number): boolean { + if (target[AclSym].ACL) return target[AclSym].ACL; - console.log("here"); - // if (target[AclSym].ACL !== AclEdit && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return false; - if (target[AclSym].ACL === AclEdit) return true; - for (const [key, value] of Object.entries(target[AclSym])) { - if (key.startsWith("ACL-")) { - if (GroupManager.Instance.currentUserGroups.includes(key.substring(4))) { - if (value === AclEdit) return true; + let effectiveAcl = AclPrivate; + let aclPresent = false; + + for (const [key, value] of Object.entries(target[AclSym])) { + if (key.startsWith("ACL-")) { + if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) { + if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) { + aclPresent = true; + effectiveAcl = value as symbol; + if (effectiveAcl === AclEdit) break; + } + } } } + return aclPresent ? effectiveAcl : AclEdit; + } + else { + return AclEdit; } - return _overrideAcl || DocServer.PlaygroundFields.includes(in_prop.toString()); } + const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { - console.log("in setter") let prop = in_prop; - // if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function - if (!testPermission(target, in_prop)) return true; + if (getEffectiveAcl(target, in_prop) !== AclEdit) { + return true; + } if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { console.log(prop + " is deprecated - switch to _" + prop); @@ -176,11 +183,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an } export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { - console.log("in getter") let prop = in_prop; - const effectiveAcl = getEffectiveAcl(target); - if (in_prop === AclSym) return _overrideAcl ? undefined : effectiveAcl; - if (effectiveAcl === AclPrivate && !_overrideAcl) return undefined; + if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; + if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; if (prop === LayoutSym) { return target.__LAYOUT__; } @@ -217,7 +222,6 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP } if (field === undefined && !ignoreProto && prop !== "proto") { const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters - console.log("here"); if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) { return getFieldImpl(proto[Self], prop, receiver, ignoreProto); } -- cgit v1.2.3-70-g09d2 From d70c9004215aea00514030be4137ccc2247b541a Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 9 Jul 2020 12:42:20 +0530 Subject: removed some unnecessary imports --- src/client/util/SharingManager.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 +-- 6 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index d64302456..fd3b2dd04 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,7 +1,7 @@ import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, DocCastAsync, DocListCast } from "../../fields/Doc"; +import { Doc, Opt, DocListCast } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 781673e59..43ffe225f 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,4 +1,4 @@ -import { Doc, Opt, DataSym, DocListCast, AclSym, AclReadonly, AclAddonly } from '../../fields/Doc'; +import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly } from '../../fields/Doc'; import { Touchable } from './Touchable'; import { computed, action, observable } from 'mobx'; import { Cast, BoolCast, ScriptCast } from '../../fields/Types'; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 6a6a475c8..032012b2d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -8,7 +8,7 @@ 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 { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc'; +import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e34ceb994..1e6966c05 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, AclSym, AclPrivate } from "../../../fields/Doc"; +import { Doc, Opt, Field, AclPrivate } from "../../../fields/Doc"; import { Cast, StrCast, NumCast } from "../../../fields/Types"; import { OmitKeys, Without, emptyPath } from "../../../Utils"; import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox"; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b9ae8b444..9dc30c683 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, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ccf83cbf9..aa89b4a10 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -226,7 +226,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - // if (!this.dataDoc[AclSym]) { // what? if (getEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; -- cgit v1.2.3-70-g09d2 From bc0d6410ac42af595cea1fb242e10e464da321ae Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 9 Jul 2020 20:41:28 +0530 Subject: change DocListCast to async in SharingManager + prevented textbox from showing up on click --- src/client/util/SharingManager.tsx | 71 +++++++++++----------- src/client/views/DocComponent.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 6 +- .../collections/collectionFreeForm/MarqueeView.tsx | 6 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/fields/Doc.ts | 16 ++--- src/fields/util.ts | 24 +++----- 9 files changed, 70 insertions(+), 69 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index fd3b2dd04..af68edab6 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,7 +1,7 @@ import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, DocListCast } from "../../fields/Doc"; +import { Doc, Opt, DocListCastAsync } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; @@ -43,19 +43,6 @@ export enum SharingPermissions { // [SharingPermissions.Edit, "green"] // ]); -// export const HierarchyMapping = new Map([ -// [SharingPermissions.None, 0], -// [SharingPermissions.View, 1], -// [SharingPermissions.Add, 2], -// [SharingPermissions.Edit, 3] - -// // ["0", SharingPermissions.None], -// // ["1", SharingPermissions.View], -// // ["2", SharingPermissions.Add], -// // ["3", SharingPermissions.Edit] - -// ]); - interface GroupOptions { label: string; options: UserOptions[]; @@ -88,8 +75,6 @@ export default class SharingManager extends React.Component<{}> { @observable private overlayOpacity = 0.4; @observable private selectedUsers: UserOptions[] | null = null; @observable private permissions: SharingPermissions = SharingPermissions.Edit; - @observable private sharedUsers: ValidatedUser[] = []; - @observable private sharedGroups: Doc[] = []; // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; @@ -162,13 +147,16 @@ export default class SharingManager extends React.Component<{}> { target[ACL] = permission; - group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(target) : group.docsShared = new List([target]); + group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List).push(target)) : group.docsShared = new List([target]); + // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(target) : group.docsShared = new List([target]); users.forEach(({ notificationDoc }) => { - if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); - else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); - + DocListCastAsync(notificationDoc[storage]).then(res => console.log(res)); + DocListCastAsync(notificationDoc[storage]).then(resolved => { + if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); + else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); + }); }); } @@ -176,7 +164,12 @@ export default class SharingManager extends React.Component<{}> { const user: ValidatedUser = this.users.find(user => user.user.email === email)!; if (group.docsShared) { - DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); + DocListCastAsync(group.docsShared).then(docsShared => { + docsShared?.forEach(doc => { + DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); + }); + }); + // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } } @@ -184,20 +177,28 @@ export default class SharingManager extends React.Component<{}> { const user: ValidatedUser = this.users.find(user => user.user.email === email)!; if (group.docsShared) { - DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + DocListCastAsync(group.docsShared).then(docsShared => { + docsShared?.forEach(doc => { + DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + }); + }); + // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } } removeGroup = (group: Doc) => { if (group.docsShared) { - DocListCast(group.docsShared).forEach(doc => { - const ACL = `ACL-${StrCast(group.groupName)}`; - doc[ACL] = "Not Shared"; + DocListCastAsync(group.docsShared).then(resolved => { + resolved?.forEach(doc => { + const ACL = `ACL-${StrCast(group.groupName)}`; + doc[ACL] = "Not Shared"; + + const members: string[] = JSON.parse(StrCast(group.members)); + const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email)); - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email)); + users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + }); - users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); }); } } @@ -217,14 +218,16 @@ export default class SharingManager extends React.Component<{}> { if (permission !== SharingPermissions.None) { - !this.sharedUsers.includes(recipient) && this.sharedUsers.push(recipient); - - Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); + console.log(target); + console.log(notificationDoc); + DocListCastAsync(notificationDoc[storage]).then(resolved => { + Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); + }); } else { - const index = this.sharedUsers.findIndex(user => user === recipient); - index !== -1 && this.sharedUsers.splice(index, 1); - Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); + DocListCastAsync(notificationDoc[storage]).then(resolved => { + Doc.IndexOf(target, resolved!) === -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); + }); } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 43ffe225f..eb58d8a3e 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { List } from '../../fields/List'; import { DateField } from '../../fields/DateField'; import { ScriptField } from '../../fields/ScriptField'; -import { getEffectiveAcl } from '../../fields/util'; +import { GetEffectiveAcl } from '../../fields/util'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) @@ -138,7 +138,7 @@ export function ViewBoxAnnotatableComponent

!docList.includes(d)); - const effectiveAcl = getEffectiveAcl(this.dataDoc); + const effectiveAcl = GetEffectiveAcl(this.dataDoc); if (added.length) { if (effectiveAcl === AclReadonly) { return false; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 032012b2d..7448ae002 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -17,7 +17,7 @@ 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, getEffectiveAcl } from '../../../fields/util'; +import { TraceMobx, GetEffectiveAcl } from '../../../fields/util'; import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -132,7 +132,7 @@ export class CollectionView extends Touchable !docList.includes(d)); - const effectiveAcl = getEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document); if (added.length) { if (effectiveAcl === AclReadonly) { return false; @@ -166,7 +166,7 @@ export class CollectionView extends Touchable { - if (getEffectiveAcl(this.props.Document) === AclEdit) { + if (GetEffectiveAcl(this.props.Document) === AclEdit) { const docs = doc instanceof Doc ? [doc] : doc as Doc[]; const targetDataDoc = this.props.Document[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b47236bea..971c501ca 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,6 +1,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc"; +import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc"; +import { GetEffectiveAcl } from "../../../../fields/util"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; @@ -276,7 +277,8 @@ export class MarqueeView extends React.Component 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 || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : + return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : (Docu render() { if (!(this.props.Document instanceof Doc)) return (null); - if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null); + if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null); if (this.props.Document.hidden) return (null); const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || 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/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index aa89b4a10..0583c7f39 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -226,7 +226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - if (getEffectiveAcl(this.dataDoc) === AclEdit) { + if (GetEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 27eabf451..e8dca5fb6 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -17,7 +17,7 @@ import { RichTextField } from "./RichTextField"; import { listSpec } from "./Schema"; import { ComputedField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; -import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, getEffectiveAcl } from "./util"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util"; import { LinkManager } from "../client/util/LinkManager"; import { SharingPermissions } from "../client/util/SharingManager"; @@ -136,10 +136,10 @@ export class Doc extends RefField { set: setter, get: getter, // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter - has: (target, key) => getEffectiveAcl(target) !== AclPrivate && key in target.__fields, + has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields, ownKeys: target => { const obj = {} as any; - if (getEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields); + if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields); runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); return Object.keys(obj); }, @@ -197,7 +197,7 @@ export class Doc extends RefField { public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } - public [ToString]() { return `Doc(${getEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; } + public [ToString]() { return `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; } public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } public get [DataSym]() { const self = this[SelfProxy]; @@ -825,7 +825,7 @@ export namespace Doc { } // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) export function IsBrushedDegreeUnmemoized(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0; return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; } export function IsBrushedDegree(doc: Doc) { @@ -834,14 +834,14 @@ export namespace Doc { })(doc); } export function BrushDoc(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetProto(doc), true); return doc; } export function UnBrushDoc(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.delete(doc); brushManager.BrushedDoc.delete(Doc.GetProto(doc)); return doc; @@ -871,7 +871,7 @@ export namespace Doc { } const highlightManager = new HighlightBrush(); export function IsHighlighted(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false; return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); } export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { diff --git a/src/fields/util.ts b/src/fields/util.ts index a1af1d3c5..f82ea26e0 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -108,15 +108,13 @@ export function OVERRIDE_ACL(val: boolean) { } let currentUserGroups: string[] = []; -let currentUserEmail: string;// = Doc.CurrentUserEmail; export function setGroups(groups: string[]) { currentUserGroups = groups; - currentUserEmail = Doc.CurrentUserEmail; } -export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { +export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { const HierarchyMapping = new Map([ [AclPrivate, 0], @@ -131,7 +129,7 @@ export function getEffectiveAcl(target: any, in_prop?: string | symbol | number) if (target[AclSym] && Object.keys(target[AclSym]).length) { - if (target.author === currentUserEmail) return AclEdit; + if (target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit; if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; @@ -141,13 +139,11 @@ export function getEffectiveAcl(target: any, in_prop?: string | symbol | number) let aclPresent = false; for (const [key, value] of Object.entries(target[AclSym])) { - if (key.startsWith("ACL-")) { - if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) { - if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) { - aclPresent = true; - effectiveAcl = value as symbol; - if (effectiveAcl === AclEdit) break; - } + if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) { + if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) { + aclPresent = true; + effectiveAcl = value as symbol; + if (effectiveAcl === AclEdit) break; } } } @@ -163,7 +159,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - if (getEffectiveAcl(target, in_prop) !== AclEdit) { + if (GetEffectiveAcl(target, in_prop) !== AclEdit) { return true; } if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { @@ -185,7 +181,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { let prop = in_prop; if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; - if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; + if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; if (prop === LayoutSym) { return target.__LAYOUT__; } @@ -222,7 +218,7 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP } if (field === undefined && !ignoreProto && prop !== "proto") { const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters - if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) { + if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) { return getFieldImpl(proto[Self], prop, receiver, ignoreProto); } return undefined; -- cgit v1.2.3-70-g09d2 From 27b0f32460245539d77bcd435627a68364e0bc0d Mon Sep 17 00:00:00 2001 From: anika-ahluwalia Date: Thu, 9 Jul 2020 12:15:42 -0500 Subject: fixed linking undo issues --- .../collectionFreeForm/CollectionFreeFormView.tsx | 96 ++++++++++++---------- src/client/views/nodes/DocumentLinksButton.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 + 3 files changed, 57 insertions(+), 47 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a3d3a210d..ffa4be4b9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1174,47 +1174,55 @@ export class CollectionFreeFormView extends CollectionSubView { - const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth()); - const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight()); - const hscale = nh ? this.props.PanelHeight() / nh : 1; - const wscale = nw ? this.props.PanelWidth() / nw : 1; + const top = this.panX(); + const left = this.panY(); + + const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); + + const scale = this.getLocalTransform().inverse().Scale; if (this._marqueeRef) { + if (this._marqueeRef.current) { + // console.log("top: " + this._marqueeRef.current.clientTop); // console.log("left: " + this._marqueeRef.current.clientLeft); // console.log("width: " + this._marqueeRef.current.clientWidth); - // console.log("client x: " + de.clientX); - - // console.log("top: " + this._marqueeRef.current.clientTop); // console.log("height: " + this._marqueeRef.current.clientHeight); - // console.log("client y: " + de.clientY); + + console.log("width: " + this._marqueeRef.current.getBoundingClientRect().width); + console.log("height: " + this._marqueeRef.current.getBoundingClientRect().width); + console.log("x: " + this._marqueeRef.current.getBoundingClientRect().x); + console.log("y: " + this._marqueeRef.current.getBoundingClientRect().y); + + // console.log("mouse x: " + de.screenX); + // console.log("mouse y: " + de.screenY); if (this._marqueeRef.current.clientWidth > 0) { if (de.clientX - 315 - this._marqueeRef.current.clientLeft < 25) { console.log("PAN left "); - if (this.canPanX) { - this.Document._panX = de.clientX - 20 - this._marqueeRef.current.clientLeft; - setTimeout(action(() => { - this.canPanX = true; - this.panX(); - }), 2500); - this.canPanX = false; - } + + // if (this.canPanX) { + // this.Document._panX = left - 5; + // setTimeout(action(() => { + // this.canPanX = true; + // this.panX(); + // }), 250); + // this.canPanX = false; + // } } else if (de.clientX - 315 - this._marqueeRef.current.clientLeft - this._marqueeRef.current.clientWidth < 25) { console.log("PAN right "); - if (this.canPanX) { - this.Document._panX = de.clientX - 315 - this._marqueeRef.current.clientLeft - - this._marqueeRef.current.clientWidth; - - setTimeout(action(() => { - this.panX(); - this.canPanX = true; - }), 2500); - this.canPanX = false; - } + + // if (this.canPanX) { + // this.Document._panX = left + 5; + // setTimeout(action(() => { + // this.panX(); + // this.canPanX = true; + // }), 250); + // this.canPanX = false; + // } } } @@ -1222,27 +1230,27 @@ export class CollectionFreeFormView extends CollectionSubView 0) { if (de.clientY - 120 - this._marqueeRef.current.clientTop < 25) { console.log("PAN top "); - if (this.canPanY) { - this.Document._panY = de.clientY - 20 - this._marqueeRef.current.clientTop; - setTimeout(action(() => { - this.canPanY = true; - this.panY(); - }), 2500); - this.canPanY = false; - } + + // if (this.canPanY) { + // this.Document._panY = top - 5; + // setTimeout(action(() => { + // this.canPanY = true; + // this.panY(); + // }), 250); + // this.canPanY = false; + // } } else if (de.clientY - 120 - this._marqueeRef.current.clientTop - this._marqueeRef.current.clientHeight < 25) { console.log("PAN bottom "); - if (this.canPanY) { - this.Document._panY = de.clientY - 120 - this._marqueeRef.current.clientTop - - this._marqueeRef.current.clientHeight; - - setTimeout(action(() => { - this.panY(); - this.canPanY = true; - }), 2500); - this.canPanY = false; - } + + // if (this.canPanY) { + // this.Document._panY = top + 5; + // setTimeout(action(() => { + // this.panY(); + // this.canPanY = true; + // }), 250); + // this.canPanY = false; + // } } diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 44e72215d..ce96eddfe 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../fields/Doc"; import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; -import { UndoManager } from "../../util/UndoManager"; +import { UndoManager, undoBatch } from "../../util/UndoManager"; import './DocumentLinksButton.scss'; import { DocumentView } from "./DocumentView"; import React = require("react"); @@ -29,7 +29,7 @@ export class DocumentLinksButton extends React.Component { if (this._linkButton.current !== null) { const linkDrag = UndoManager.StartBatch("Drag Link"); @@ -56,7 +56,7 @@ export class DocumentLinksButton extends React.Component { setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { if (doubleTap && this.props.InMenu) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fc63dfbf5..30e0738bf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -944,6 +944,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } + + function addLinkMark(node: Node, title: string, linkId: string) { if (!node.isText) { const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); -- cgit v1.2.3-70-g09d2 From bf8c338e662327b39cdef3f90c436447e48d2807 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 9 Jul 2020 13:54:07 -0400 Subject: fixed name::portal syntax to make link properly --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fc63dfbf5..962d0012f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -183,7 +183,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); + else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id); }); }); }); -- cgit v1.2.3-70-g09d2 From 1d7fb12118db997ed411fa967e832c2a5f741584 Mon Sep 17 00:00:00 2001 From: anika-ahluwalia Date: Thu, 9 Jul 2020 12:56:57 -0500 Subject: trying to add linked text in menu --- src/client/documents/Documents.ts | 6 +++++- src/client/views/linking/LinkMenuGroup.tsx | 2 ++ src/client/views/linking/LinkMenuItem.scss | 8 ++++++++ src/client/views/linking/LinkMenuItem.tsx | 3 ++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++++- 5 files changed, 22 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fa85d58f0..565e7c25d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -909,7 +909,7 @@ export namespace DocUtils { DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline")); } - export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) { + export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, linkedText?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; if (target.doc === Doc.UserDoc()) return undefined; @@ -921,6 +921,10 @@ export namespace DocUtils { Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)"); Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)"); + if (linkedText) { + Doc.GetProto(linkDoc).linkedText = linkedText; + } + return linkDoc; } diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index ec17776e3..2f6b75437 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -82,11 +82,13 @@ export class LinkMenuGroup extends React.Component { return (

+ {/*

{this.props.groupType}:

{this.props.groupType === "*" || this.props.groupType === "" ? <> : this.viewGroupAsTable(this.props.groupType)}
*/} +
{groupItems}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index 9f1f82ce2..6a6ea7fa0 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -21,6 +21,14 @@ padding: 4px 2px; //display: inline; + .linkMenu-source-title { + text-decoration: none; + color: rgb(43, 43, 43); + font-size: 7px; + padding-bottom: 0.75px; + } + + .linkMenu-destination-title { text-decoration: none; color: rgb(85, 120, 196); diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 57993d240..b7cd50b7e 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -195,6 +195,8 @@ export class LinkMenuItem extends React.Component { onPointerDown={this.onLinkButtonDown}>
+ {this.props.linkDoc.linkedText ?

+ Source: {StrCast(this.props.linkDoc.linkedText)}

: null}

{StrCast(this.props.destinationDoc.title)}

@@ -208,7 +210,6 @@ export class LinkMenuItem extends React.Component {
-
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 30e0738bf..7fd0d89a1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -174,6 +174,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp linkOnDeselect: Map = new Map(); doLinkOnDeselect() { + + console.log("link on deselect"); Array.from(this.linkOnDeselect.entries()).map(entry => { const key = entry[0]; const value = entry[1]; @@ -183,7 +185,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); + else { + const linkDoc = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); + } }); }); }); -- cgit v1.2.3-70-g09d2 From 3d0c64cf9979f739177b0efd9970ad0e0a9fa3d0 Mon Sep 17 00:00:00 2001 From: anika-ahluwalia Date: Thu, 9 Jul 2020 17:00:12 -0500 Subject: small changes --- src/client/documents/Documents.ts | 6 +----- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 8 +++++--- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 ++ 4 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 565e7c25d..fa85d58f0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -909,7 +909,7 @@ export namespace DocUtils { DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline")); } - export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, linkedText?: string) { + export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; if (target.doc === Doc.UserDoc()) return undefined; @@ -921,10 +921,6 @@ export namespace DocUtils { Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)"); Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)"); - if (linkedText) { - Doc.GetProto(linkDoc).linkedText = linkedText; - } - return linkDoc; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 965bfdf24..9d79c0c89 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1181,17 +1181,19 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.destinationDoc.type === "screenshot" ? "photo-video" : this.props.destinationDoc.type === "webcam" ? "video" : this.props.destinationDoc.type === "audio" ? "microphone" : - this.props.destinationDoc.type === "button" ? "lightning" : + this.props.destinationDoc.type === "button" ? "bolt" : this.props.destinationDoc.type === "presentation" ? "tv" : this.props.destinationDoc.type === "query" ? "search" : this.props.destinationDoc.type === "script" ? "terminal" : diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index bff6f1c8c..4f6927d3d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -176,9 +176,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp doLinkOnDeselect() { console.log("link on deselect"); + Array.from(this.linkOnDeselect.entries()).map(entry => { const key = entry[0]; const value = entry[1]; + const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); DocServer.GetRefField(value).then(doc => { DocServer.GetRefField(id).then(linkDoc => { -- cgit v1.2.3-70-g09d2 From 20ab61d762e4c92e9ace6eb9b577667a9a8dc1e3 Mon Sep 17 00:00:00 2001 From: anika-ahluwalia Date: Fri, 10 Jul 2020 12:05:53 -0500 Subject: fixed tooltip error, label styling --- src/client/views/linking/LinkMenuItem.scss | 3 +++ src/client/views/linking/LinkMenuItem.tsx | 19 +++++++++++++------ src/client/views/nodes/DocumentLinksButton.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++++---- .../nodes/formattedText/FormattedTextBoxComment.tsx | 6 +++--- 5 files changed, 26 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index f759aef5e..3ecb306f9 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -77,6 +77,9 @@ color: rgb(95, 97, 102); font-size: 10px; margin-left: 20px; + max-width: 125px; + height: auto; + white-space: break-spaces; } p { diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index f1a744ff2..f7d189b20 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -15,7 +15,8 @@ import { setupMoveUpEvents, emptyFunction } from '../../../Utils'; import { DocumentView } from '../nodes/DocumentView'; import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; -import Tooltip from '@material-ui/core/Tooltip'; +import { Tooltip } from '@material-ui/core'; +import { RichTextField } from '../../../fields/RichTextField'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash); @@ -203,6 +204,12 @@ export class LinkMenuItem extends React.Component { console.log(StrCast(this.props.destinationDoc.title).length); + // ... + // from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText) + // ... + const source = this.props.sourceDoc.type === "rtf" ? this.props.linkDoc.storedText ? + "stored text would show up here" : undefined : undefined; + return (
@@ -218,8 +225,8 @@ export class LinkMenuItem extends React.Component { onPointerDown={this.onLinkButtonDown}>
- {this.props.linkDoc.linkedText ?

- Source: {StrCast(this.props.linkDoc.linkedText)}

: null} + {source ?

+ Source: {source}

: null}
@@ -236,16 +243,16 @@ export class LinkMenuItem extends React.Component {
: <>} -
+
-
+
-
+
{/*
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index f61ae2dd0..22431117e 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -150,7 +150,7 @@ export class DocumentLinksButton extends React.Component + const linkButton =
{ const key = entry[0]; const value = entry[1]; @@ -186,11 +184,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DocServer.GetRefField(id).then(linkDoc => { this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); - if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id); + if (linkDoc) { + (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; + } else { + DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id); + } }); }); }); + this.linkOnDeselect.clear(); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index f1602bfbc..fa2548cb5 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -21,7 +21,7 @@ import { action } from "mobx"; import { LinkManager } from "../../../util/LinkManager"; import { LinkDocPreview } from "../LinkDocPreview"; import { DocumentLinksButton } from "../DocumentLinksButton"; -import Tooltip from "@material-ui/core/Tooltip"; +import { Tooltip } from "@material-ui/core"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -271,14 +271,14 @@ export class FormattedTextBoxComment {
-
this._deleteRef = r}>
-
this._followRef = r}> -- cgit v1.2.3-70-g09d2 From 0b0183de3a13649819983203e2aba1dc6b84fdb1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 12 Jul 2020 23:34:34 -0400 Subject: fixed updating RichTextMenu with proper values for fontFamily, size, bullet, and alignment --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 15 +-- src/client/util/DragManager.ts | 2 +- src/client/views/animationtimeline/Timeline.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/SliderBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 7 +- .../views/nodes/formattedText/RichTextMenu.tsx | 111 ++++++++++++--------- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/fields/documentSchemas.ts | 2 +- 13 files changed, 88 insertions(+), 67 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 72b716492..a415e17c8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -123,7 +123,7 @@ export interface DocumentOptions { isBackground?: boolean; isLinkButton?: boolean; _columnWidth?: number; - _fontSize?: number; + _fontSize?: string; _fontFamily?: string; curPage?: number; currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ce910c062..39781a5ce 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -211,7 +211,7 @@ export class CurrentUserUtils { details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 }; - const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 }; + const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" }; const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" }; const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); @@ -525,13 +525,13 @@ export class CurrentUserUtils { // Sets up the title of the button static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { ...opts, - dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)" + dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)" }) as any as Doc // Sets up the description of the button static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { ...opts, - dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, + dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, }) as any as Doc @@ -598,7 +598,7 @@ export class CurrentUserUtils { _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true })) as any as Doc; doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 35, _height: 25, title: "Tools", _fontSize: 10, + _width: 35, _height: 25, title: "Tools", _fontSize: "10pt", letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: toolsStack, onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'), @@ -663,7 +663,7 @@ export class CurrentUserUtils { lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same" })) as any as Doc; doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 50, _height: 25, title: "Library", _fontSize: 10, targetDropAction: "same", + _width: 50, _height: 25, title: "Library", _fontSize: "10pt", targetDropAction: "same", letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: libraryStack, onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'), @@ -681,7 +681,7 @@ export class CurrentUserUtils { static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) { if (doc["tabs-button-search"] === undefined) { doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 50, _height: 25, title: "Search", _fontSize: 10, + _width: 50, _height: 25, title: "Search", _fontSize: "10pt", letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc, searchFileTypes: new List([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]), @@ -824,7 +824,8 @@ export class CurrentUserUtils { doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); doc.activeArrowEnd = StrCast(doc.activeArrowEnd, ""); doc.activeDash = StrCast(doc.activeDash, "0"); - doc.fontSize = NumCast(doc.fontSize, 12); + doc.fontSize = StrCast(doc.fontSize, "12pt"); + doc.fontFamily = StrCast(doc.fontFamily, "Arial"); doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 64c3d8458..5f34509a1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -323,7 +323,7 @@ export namespace DragManager { dragLabel = document.createElement("div"); dragLabel.className = "dragManager-dragLabel"; dragLabel.style.zIndex = "100001"; - dragLabel.style.fontSize = "10"; + dragLabel.style.fontSize = "10pt"; dragLabel.style.position = "absolute"; // dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar dragDiv.appendChild(dragLabel); diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index f54bd3aff..eac30ca75 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -385,10 +385,10 @@ export class Timeline extends React.Component { const ttime = `Total: ${this.toReadTime(this._time)}`; return (
-
+
{ctime}
-
+
{ttime}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2191021d2..ef6ea8f99 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1034,7 +1034,7 @@ export class CollectionFreeFormView extends CollectionSubView val === undefined) ? undefined : { ele:
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b47236bea..2db665337 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -129,7 +129,7 @@ export class MarqueeView extends React.Component(Docu background: finalColor, opacity: finalOpacity, fontFamily: StrCast(this.Document._fontFamily, "inherit"), - fontSize: Cast(this.Document._fontSize, "number", null) + fontSize: Cast(this.Document._fontSize, "string", null) }}> {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <> {this.innards} diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 360ead18e..836ef4149 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -67,7 +67,7 @@ export class LabelBox extends ViewBoxBaseComponent
this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0); - } else if (FormattedTextBoxComment.textBox === this) { + setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected + if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) { setTimeout(() => FormattedTextBoxComment.Hide(), 0); } const selPad = this.props.isSelected() ? -10 : 0; @@ -1376,7 +1375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"), pointerEvents: interactive ? undefined : "none", - fontSize: Cast(this.layoutDoc._fontSize, "number", null), + fontSize: Cast(this.layoutDoc._fontSize, "string", null), fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), transition: "opacity 1s" }} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 1a8a4ceb7..a70f879ff 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -11,7 +11,7 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../../fields/Doc"; import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; -import { Cast, StrCast, BoolCast } from "../../../../fields/Types"; +import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types"; import { unimplementedFunction, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { LinkManager } from "../../../util/LinkManager"; @@ -112,6 +112,7 @@ export default class RichTextMenu extends AntimodeMenu { { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "", command: this.changeListType }, //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, ]; @@ -217,7 +218,7 @@ export default class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveAlignment() { - if (this.view) { + if (this.view && this.TextView.props.isSelected()) { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph) { @@ -230,15 +231,18 @@ export default class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveListStyle() { - if (this.view) { + if (this.view && this.TextView.props.isSelected()) { const path = (this.view.state.selection.$from as any).path; for (let i = 0; i < path.length; i += 3) { if (path[i].type === this.view.state.schema.nodes.ordered_list) { return path[i].attrs.mapStyle; } } + if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) { + return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle; + } } - return "decimal"; + return ""; } // finds font sizes and families in selection @@ -247,16 +251,21 @@ export default class RichTextMenu extends AntimodeMenu { const activeFamilies: string[] = []; const activeSizes: string[] = []; - const state = this.view.state; - const pos = this.view.state.selection.$from; - const ref_node = this.reference_node(pos); - if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - ref_node.marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); - }); + if (this.TextView.props.isSelected()) { + const state = this.view.state; + const pos = this.view.state.selection.$from; + const ref_node = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => { + m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); + }); + } + !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily)))); + !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize)))); } - + !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); + !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize))); return { activeFamilies, activeSizes }; } @@ -269,14 +278,14 @@ export default class RichTextMenu extends AntimodeMenu { //finds all active marks on selection in given group getActiveMarksOnSelection() { - if (!this.view) return; + let activeMarks: MarkType[] = []; + if (!this.view || !this.TextView.props.isSelected()) return activeMarks; const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); //current selection const { empty, ranges, $to } = this.view.state.selection as TextSelection; const state = this.view.state; - let activeMarks: MarkType[] = []; if (!empty) { activeMarks = markGroup.filter(mark => { const has = false; @@ -357,13 +366,9 @@ export default class RichTextMenu extends AntimodeMenu { createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { - return label === activeOption ? - : - ; + return ; } - return label === activeOption ? - : - ; + return ; }); const self = this; @@ -372,37 +377,42 @@ export default class RichTextMenu extends AntimodeMenu { e.preventDefault(); self.TextView.endUndoTypingBatch(); options.forEach(({ label, mark, command }) => { - if (e.target.value === label) { - UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown"); + if (e.target.value === label && mark) { + if (!self.TextView.props.isSelected()) { + switch (mark.type) { + case schema.marks.pFontFamily: Doc.UserDoc().fontFamily = mark.attrs.family; break; + case schema.marks.pFontSize: Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"; break; + } + } + else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown"); } }); } - return ; + return ; } - createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { - const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1"; + createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element { + const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : ""; const items = options.map(({ title, label, hidden, style }) => { if (hidden) { - return label === activeOption ? - : - ; + return ; } - return label === activeOption ? - : - ; + return ; }); const self = this; function onChange(val: string) { self.TextView.endUndoTypingBatch(); options.forEach(({ label, node, command }) => { - if (val === label) { - UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown"); + if (val === label && node) { + if (self.TextView.props.isSelected()) { + UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown"); + setter(val); + } } }); } - return ; + return ; } changeFontSize = (mark: Mark, view: EditorView) => { @@ -416,10 +426,21 @@ export default class RichTextMenu extends AntimodeMenu { // TODO: remove doesn't work //remove all node type and apply the passed-in one to the selected text changeListType = (nodeType: Node | undefined) => { - if (!this.view) return; + if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return; + + const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; + let inList: any = undefined; + let fromList = -1; + let path: any = Array.from((this.view.state.selection.$from as any).path); + for (let i = 0; i < path.length; i++) { + if (path[i]?.type === schema.nodes.ordered_list) { + inList = path[i]; + fromList = path[i - 1]; + } + } const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); @@ -427,12 +448,12 @@ export default class RichTextMenu extends AntimodeMenu { this.view!.dispatch(tx2); })) { const tx2 = this.view.state.tr; - if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to); + if (nodeType && (inList || nextIsOL)) { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, + inList ? fromList + inList.nodeSize : this.view.state.selection.to); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); - - this.view.dispatch(tx3.setSelection(new NodeSelection(tx3.doc.resolve(this.view.state.selection.$from.pos)))); + this.view.dispatch(tx3); } } } @@ -448,13 +469,13 @@ export default class RichTextMenu extends AntimodeMenu { return true; } alignCenter = (state: EditorState, dispatch: any) => { - return this.alignParagraphs(state, "center", dispatch); + return this.TextView.props.isSelected() && this.alignParagraphs(state, "center", dispatch); } alignLeft = (state: EditorState, dispatch: any) => { - return this.alignParagraphs(state, "left", dispatch); + return this.TextView.props.isSelected() && this.alignParagraphs(state, "left", dispatch); } alignRight = (state: EditorState, dispatch: any) => { - return this.alignParagraphs(state, "right", dispatch); + return this.TextView.props.isSelected() && this.alignParagraphs(state, "right", dispatch); } alignParagraphs(state: EditorState, align: "left" | "right" | "center", dispatch: any) { @@ -914,7 +935,7 @@ export default class RichTextMenu extends AntimodeMenu { {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
, - this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"), + this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes", action((val: string) => this.activeListType = val)), this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer), this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote), this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule), diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index ca30dde9d..ef0fead4a 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -90,7 +90,7 @@ export class RichTextRules { textDoc.inlineTextCount = numInlines + 1; const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" }); + const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" }); textDocInline.title = inlineFieldKey; // give the annotation its own title textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index c1659d4d5..61a37ef8a 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -55,7 +55,7 @@ export const documentSchema = createSchema({ _columnsHideIfEmpty: "boolean", // whether empty stacking view column headings should be hidden _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views - _fontSize: "number", + _fontSize: "string", _fontFamily: "string", _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar -- cgit v1.2.3-70-g09d2 From db15b1d27a639af7a65f72dd5e4b6ea298412315 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 13 Jul 2020 11:46:35 -0400 Subject: fixed issues with ACLs and writing to playground fields when you have Edit permission. Also fixed text editing by fixing fetchProto to use untracked references to fields --- src/client/util/SharingManager.tsx | 3 ++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/Doc.ts | 12 +++++------- src/fields/util.ts | 8 +++++--- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index af68edab6..050ff0c4e 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -146,6 +146,7 @@ export default class SharingManager extends React.Component<{}> { const ACL = `ACL-${StrCast(group.groupName)}`; target[ACL] = permission; + Doc.GetProto(target)[ACL] = permission; group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List).push(target)) : group.docsShared = new List([target]); // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(target) : group.docsShared = new List([target]); @@ -215,7 +216,7 @@ export default class SharingManager extends React.Component<{}> { // const permissions: { [key: string]: number } = target[ACL] ? JSON.parse(StrCast(target[ACL])) : {}; target[ACL] = permission; - + Doc.GetProto(target)[ACL] = permission; if (permission !== SharingPermissions.None) { console.log(target); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0af941182..01fbcb020 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -237,7 +237,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._applyingChange = true; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (json !== curLayout?.Data) { + if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) { !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize)); !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily)); this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index e8dca5fb6..8ab4735a7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, ObservableMap, runInAction } from "mobx"; +import { action, computed, observable, ObservableMap, runInAction, untracked } from "mobx"; import { computedFn } from "mobx-utils"; import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; @@ -110,15 +110,13 @@ const AclMap = new Map([ export function fetchProto(doc: Doc) { if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions + untracked(() => { + const permissions: { [key: string]: symbol } = {}; - const permissions: { [key: string]: symbol } = {}; + Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); - Object.keys(doc).forEach(key => { - if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!; + if (Object.keys(permissions).length) doc[AclSym] = permissions; }); - - - if (Object.keys(permissions).length) doc[AclSym] = permissions; } if (doc.proto instanceof Promise) { diff --git a/src/fields/util.ts b/src/fields/util.ts index ebfc3933a..be7736413 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -70,8 +70,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); - const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly); - const writeToServer = (sameAuthor || (writeMode === DocServer.WriteMode.Default)) && !playgroundMode; + const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly); + const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode; if (writeToDoc) { if (value === undefined) { @@ -91,8 +91,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue }); + return true; } - return true; + return false; }); let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; @@ -126,6 +127,7 @@ export function setGroups(groups: string[]) { } export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { + if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit; const HierarchyMapping = new Map([ [AclPrivate, 0], -- cgit v1.2.3-70-g09d2 From 0e2a5196b47c05eab74d0aae06c2db4a9528f0fb Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 13 Jul 2020 20:44:03 -0400 Subject: switched context menus to be less hierarchical. --- src/client/util/CurrentUserUtils.ts | 2 - src/client/util/SettingsManager.scss | 2 +- src/client/util/SettingsManager.tsx | 14 ++++++- src/client/views/ContextMenuItem.tsx | 6 +++ .../views/collections/CollectionCarousel3DView.tsx | 13 +----- .../views/collections/CollectionCarouselView.tsx | 14 +------ .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 6 +-- .../collectionFreeForm/FormatShapePane.tsx | 26 ++++++------ .../collectionGrid/CollectionGridView.tsx | 5 +-- src/client/views/nodes/DocumentView.tsx | 38 ++++++++--------- src/client/views/nodes/ImageBox.tsx | 48 +++++++++++----------- src/client/views/nodes/LabelBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 48 +++++++++++----------- 14 files changed, 111 insertions(+), 115 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 39781a5ce..048ab9edc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -423,8 +423,6 @@ export class CurrentUserUtils { // { 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)' }, - { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "heading", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index 13c65042c..6d394a38d 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -56,7 +56,7 @@ .settings-type { display: flex; flex-direction: column; - flex-basis: 30%; + flex-basis: 45%; } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 9888cce48..f7ca3942b 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -11,6 +11,8 @@ import { Networking } from "../Network"; import { CurrentUserUtils } from "./CurrentUserUtils"; import { Utils } from "../../Utils"; import { Doc } from "../../fields/Doc"; +import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager"; +import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; library.add(fa.faWindowClose); @@ -83,6 +85,14 @@ export default class SettingsManager extends React.Component<{}> { noviceToggle = (event: any) => { Doc.UserDoc().noviceMode = !Doc.UserDoc().noviceMode; } + @action + googleAuthorize = (event: any) => { + GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true) + } + @action + hypothesisAuthorize = (event: any) => { + HypothesisAuthenticationManager.Instance.fetchAccessToken(true) + } private get settingsInterface() { return ( @@ -96,7 +106,9 @@ export default class SettingsManager extends React.Component<{}> {
- + + + diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 99840047f..68ebd8e14 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -19,6 +19,7 @@ export interface OriginalMenuProps { export interface SubmenuProps { description: string; subitems: ContextMenuProps[]; + noexpand?: boolean; icon: IconProp; //maybe should be optional (icon?) closeMenu?: () => void; } @@ -96,6 +97,11 @@ export class ContextMenuItem extends React.Component {this._items.map(prop => )}
; + if (!("noexpand" in this.props)) { + return <> + {this._items.map(prop => )} + ; + } return (
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 1344b70f4..8f1cd5311 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -108,17 +108,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume }, 1500); } - onContextMenu = (e: React.MouseEvent): void => { - // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped()) { - ContextMenu.Instance.addItem({ - description: "Make Hero Image", event: () => { - const index = NumCast(this.layoutDoc._itemIndex); - (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField); - }, icon: "plus" - }); - } - } _downX = 0; _downY = 0; onPointerDown = (e: React.PointerEvent) => { @@ -184,7 +173,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume const index = NumCast(this.layoutDoc._itemIndex); const translateX = (1 - index) / this.childLayoutPairs.length * 100; - return
+ return
{this.content}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index bd0e4fc9a..b3fecfb91 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -83,18 +83,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) ; } - - onContextMenu = (e: React.MouseEvent): void => { - // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped()) { - ContextMenu.Instance?.addItem({ - description: "Make Hero Image", event: () => { - const index = NumCast(this.layoutDoc._itemIndex); - (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField); - }, icon: "plus" - }); - } - } _downX = 0; _downY = 0; onPointerDown = (e: React.PointerEvent) => { @@ -119,7 +107,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) } render() { - return
+ return
{this.content} {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 26c41f524..dbd39d8df 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -780,7 +780,7 @@ export class CollectionTreeView extends CollectionSubView UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index df21d6a28..cbd1ac9af 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -272,7 +272,7 @@ export class CollectionView extends Touchable this._isLightboxOpen = true), icon: "eye" }); - !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" }); + !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" }); } onContextMenu = (e: React.MouseEvent): void => { @@ -294,7 +294,7 @@ export class CollectionView extends Touchable this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" }); } - layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); + !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" }); @@ -314,7 +314,7 @@ export class CollectionView extends Touchable this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), })); - !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); if (!Doc.UserDoc().noviceMode) { const more = cm.findByDescription("More..."); diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx index 56ba9e96a..9d9ce7f36 100644 --- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx @@ -11,6 +11,7 @@ import { DocumentType } from "../../../documents/DocumentTypes"; import { SelectionManager } from "../../../util/SelectionManager"; import AntimodeMenu from "../../AntimodeMenu"; import "./FormatShapePane.scss"; +import { undoBatch } from "../../../util/UndoManager"; @observer export default class FormatShapePane extends AntimodeMenu { @@ -118,6 +119,7 @@ export default class FormatShapePane extends AntimodeMenu { } } + @undoBatch @action rotate = (degrees: number) => { this.selectedInk?.forEach(action(inkView => { @@ -160,7 +162,7 @@ export default class FormatShapePane extends AntimodeMenu { colorPicker(setter: (color: string) => {}) { return
{this._palette.map(color => - )}
; @@ -171,11 +173,11 @@ export default class FormatShapePane extends AntimodeMenu { type="text" value={value} onChange={e => setter(e.target.value)} autoFocus /> -
- ; @@ -183,7 +185,7 @@ export default class FormatShapePane extends AntimodeMenu { colorButton(value: string, setter: () => {}) { return <> - @@ -206,10 +208,10 @@ export default class FormatShapePane extends AntimodeMenu { @computed get propertyGroupItems() { const fillCheck =
- this.unFilled = true)} /> + this.unFilled = true))} /> No Fill
- this.solidFil = true)} /> + this.solidFil = true))} /> Solid Fill

{this.solidFil ? "Color" : ""} @@ -218,22 +220,22 @@ export default class FormatShapePane extends AntimodeMenu {
; const markers = <> - this.markHead = this.markHead ? "" : "arrow")} /> + this.markHead = this.markHead ? "" : "arrow"))} /> Arrow Head
- this.markTail = this.markTail ? "" : "arrow")} /> + this.markTail = this.markTail ? "" : "arrow"))} /> Arrow End
; const lineCheck =
- this.unStrokd = true)} /> + this.unStrokd = true))} /> No Line
- this.solidStk = true)} /> + this.solidStk = true))} /> Solid Line
- this.dashdStk = "2")} /> + this.dashdStk = "2"))} /> Dash Line

@@ -253,7 +255,7 @@ export default class FormatShapePane extends AntimodeMenu {

Width {this.widInput}

- this._lock = !this._lock)} /> + this._lock = !this._lock))} /> Lock Ratio

Rotation {this.rotInput} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 2015ca930..188b88c41 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -1,7 +1,7 @@ import { action, computed, Lambda, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; -import { Doc, Opt } from '../../../../fields/Doc'; +import { Doc, Opt, WidthSym } from '../../../../fields/Doc'; import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; import { makeInterface } from '../../../../fields/Schema'; @@ -248,8 +248,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { */ onContextMenu = () => { const displayOptionsMenu: ContextMenuProps[] = []; - displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" }); - displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" }); + displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" }); ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6ef976ceb..a586e81d8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -770,6 +770,7 @@ export class DocumentView extends DocComponent(Docu const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); + optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); @@ -783,14 +784,14 @@ export class DocumentView extends DocComponent(Docu 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.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" }); 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" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); const funcs: ContextMenuProps[] = []; if (this.layoutDoc.onDragStart) { funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined }); - cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" }); + cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" }); } const more = cm.findByDescription("More..."); @@ -817,29 +818,28 @@ export class DocumentView extends DocComponent(Docu // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); }); + moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); } - moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" }); !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!); - const help = cm.findByDescription("Help..."); - const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : []; - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); - helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); - cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" }); - - const existingAcls = cm.findByDescription("Privacy..."); - const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; - aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" }); - !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" }); + // const help = cm.findByDescription("Help..."); + // const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : []; + // helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); + // helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); + // cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" }); + + // const existingAcls = cm.findByDescription("Privacy..."); + // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; + // aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); + // aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); + // aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); + // aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" }); + // aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" }); + // aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" }); + // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" }); // const recommender_subitems: ContextMenuProps[] = []; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d16aa528c..4eba21eab 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -157,29 +157,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); - funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); - // funcs.push({ - // description: "Reset Native Dimensions", event: action(async () => { - // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); - // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); - // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { - // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; - // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); - // } else { - // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); - // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; - // } - // }), icon: "expand-arrows-alt" - // }); - - const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); - const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; - modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); - modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); - //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); - !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); + funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" }); + if (!Doc.UserDoc().noviceMode) { + funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); + funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); + // funcs.push({ + // description: "Reset Native Dimensions", event: action(async () => { + // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); + // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); + // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { + // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; + // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); + // } else { + // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); + // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; + // } + // }), icon: "expand-arrows-alt" + // }); + + const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); + const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; + modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); + modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); + //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); + !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); + } ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 836ef4149..0dfbdc5cf 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); - appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); + appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); const uicontrols: ContextMenuProps[] = []; uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); @@ -489,10 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" }); - appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); + appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" }); this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - appearanceItems.push({ + + !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + + const funcs: ContextMenuProps[] = []; + + const highlighting: ContextMenuProps[] = []; + ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + highlighting.push({ + description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + e.stopPropagation(); + if (FormattedTextBox._highlights.indexOf(option) === -1) { + FormattedTextBox._highlights.push(option); + } else { + FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); + } + this.updateHighlights(); + }, icon: "expand-arrows-alt" + })); + funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); + funcs.push({ description: "Convert to be a template style", event: () => { if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); @@ -517,29 +536,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); - !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - const funcs: ContextMenuProps[] = []; - - //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - - const highlighting: ContextMenuProps[] = []; - ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => - highlighting.push({ - description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { - e.stopPropagation(); - if (FormattedTextBox._highlights.indexOf(option) === -1) { - FormattedTextBox._highlights.push(option); - } else { - FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); - } - this.updateHighlights(); - }, icon: "expand-arrows-alt" - })); - funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); - + funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); this._downX = this._downY = Number.NaN; } -- cgit v1.2.3-70-g09d2 From c63b780580b5cd9c079da361ff65019641782226 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 17 Jul 2020 03:26:29 -0400 Subject: fixed issues with embedded text boxes in text boxes when displayed multiple times (alias or minimap). fixed richtextmenu to always "point to" the selected text box. --- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 20 ++++++++++++-------- .../views/nodes/formattedText/RichTextMenu.tsx | 9 +++++---- .../views/nodes/formattedText/RichTextSchema.tsx | 10 +++++++++- 4 files changed, 27 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index be1ef56d0..8938541ac 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -916,7 +916,7 @@ export class DockedFrameRenderer extends React.Component { docFilters={returnEmptyFilter} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> - {document._viewType === CollectionViewType.Freeform ? this.renderMiniMap() : (null)} + {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)} ; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f87b28c7f..30e1fa9cd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -232,6 +232,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); + let unchanged = true; if (!this.dataDoc[AclSym]) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; @@ -243,21 +244,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + unchanged = false; } } else { // if we've deleted all the text in a note driven by a template, then restore the template data this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + unchanged = false; } this._applyingChange = false; + if (!unchanged) { + this.updateTitle(); + this.tryUpdateHeight(); + } } } else { const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!); json.selection = state.toJSON().selection; this._editorView.updateState(EditorState.fromJSON(this.config, json)); } - this.updateTitle(); - this.tryUpdateHeight(); } } @@ -1036,7 +1041,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView?.destroy(); } - static _downEvent: any; + _downEvent: any; _downX = 0; _downY = 0; _break = false; @@ -1056,7 +1061,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._downX = e.clientX; this._downY = e.clientY; this.doLinkOnDeselect(); - FormattedTextBox._downEvent = true; + this._downEvent = true; FormattedTextBoxComment.textBox = this; if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { e.preventDefault(); @@ -1072,8 +1077,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onPointerUp = (e: React.PointerEvent): void => { - if (!FormattedTextBox._downEvent) return; - FormattedTextBox._downEvent = false; + if (!this._downEvent) return; + this._downEvent = false; if (!(e.nativeEvent as any).formattedHandled) { const editor = this._editorView!; FormattedTextBoxComment.textBox = this; @@ -1092,7 +1097,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onDoubleClick = (e: React.MouseEvent): void => { this.doLinkOnDeselect(); - FormattedTextBox._downEvent = true; FormattedTextBoxComment.textBox = this; if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { e.preventDefault(); @@ -1244,7 +1248,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp richTextMenuPlugin() { return new Plugin({ view(newView) { - RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); + RichTextMenu.Instance?.changeView(newView); return RichTextMenu.Instance; } }); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 63f6fdc54..9890ef2c1 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -156,7 +156,9 @@ export default class RichTextMenu extends AntimodeMenu { @action changeView(view: EditorView) { - this.view = view; + if ((view as any)?.TextView?.props.isSelected(true)) { + this.view = view; + } } update(view: EditorView, lastState: EditorState | undefined) { @@ -165,8 +167,7 @@ export default class RichTextMenu extends AntimodeMenu { @action public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { - if (!view) { - console.log("no editor? why?"); + if (!view || !(view as any).TextView?.props.isSelected(true)) { return; } this.view = view; @@ -318,7 +319,7 @@ export default class RichTextMenu extends AntimodeMenu { } destroy() { - this.fadeOut(true); + !this.TextView.props.isSelected(false) && this.fadeOut(true); } @action diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index a989abd6a..7a50ec3af 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -54,6 +54,8 @@ export class DashDocView { this._dashSpan.style.height = node.attrs.height; this._dashSpan.style.position = "absolute"; this._dashSpan.style.display = "inline-block"; + this._dashSpan.style.left = "0"; + this._dashSpan.style.top = "0"; this._dashSpan.style.whiteSpace = "normal"; this._dashSpan.onpointerleave = () => { @@ -160,7 +162,13 @@ export class DashDocView { if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + if (getPos() !== undefined) { + const node = view.state.tr.doc.nodeAt(getPos()); + if (node.attrs.width !== dashDoc._width + "px" || + node.attrs.height !== dashDoc._height + "px") { + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } + } } catch (e) { console.log(e); } -- cgit v1.2.3-70-g09d2 From 49e1dccee78af835ef48723bb708f1c2b47c9228 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 22 Jul 2020 21:09:20 -0400 Subject: fixed highlighting of text based on author. made download /import document novice menu items. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 69 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 20 +++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 15 ++--- src/client/views/nodes/formattedText/marks_rts.ts | 10 ++-- 4 files changed, 58 insertions(+), 56 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bee9e7009..dc32ecb07 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1258,41 +1258,44 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); - optionItems.push({ - description: "Import document", icon: "upload", event: ({ x, y }) => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".zip"; - input.onchange = async _e => { - const upload = Utils.prepend("/uploadDoc"); - const formData = new FormData(); - const file = input.files && input.files[0]; - if (file) { - formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json !== "error") { - const doc = await DocServer.GetRefField(json); - if (doc instanceof Doc) { - const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(doc); - setTimeout(() => { - SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { - docs.docs.forEach(d => LinkManager.Instance.addLink(d)); - }) - }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. - } - } - } - }; - input.click(); - } - }); + } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); - + const mores = ContextMenu.Instance.findByDescription("More..."); + const moreItems = mores && "subitems" in mores ? mores.subitems : []; + moreItems.push({ + description: "Import document", icon: "upload", event: ({ x, y }) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".zip"; + input.onchange = async _e => { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + const file = input.files && input.files[0]; + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc) { + const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument?.(doc); + setTimeout(() => { + SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { + docs.docs.forEach(d => LinkManager.Instance.addLink(d)); + }) + }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. + } + } + } + }; + input.click(); + } + }); + !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); } @observable showTimeline = false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3b46b70ea..748938699 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -782,6 +782,16 @@ export class DocumentView extends DocComponent(Docu const more = cm.findByDescription("More..."); const moreItems = more && "subitems" in more ? more.subitems : []; + moreItems.push({ + description: "Download document", icon: "download", event: async () => { + Doc.Zip(this.props.Document); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); + } + }); if (!Doc.UserDoc().noviceMode) { 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" }); @@ -791,16 +801,6 @@ export class DocumentView extends DocComponent(Docu moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" }); moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" }); } - moreItems.push({ - description: "Download document", icon: "download", event: async () => { - Doc.Zip(this.props.Document); - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); - } - }); moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); } GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 7906e2533..e703a81e2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -425,16 +425,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); } if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" }); } if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" }); } if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" }); } if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" }); } if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); @@ -1303,9 +1303,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); - this._lastTimedMark = mark; - // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); + if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { + const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); + } if (!this._undoTyping) { this.startUndoTypingBatch(); diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 3d7d71b14..f95f46104 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -258,9 +258,7 @@ export const marks: { [index: string]: MarkSpec } = { }, parseDOM: [{ style: 'background: yellow' }], toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.selected ? "orange" : "yellow"}` - }]; + return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }]; } }, @@ -277,8 +275,8 @@ export const marks: { [index: string]: MarkSpec } = { const min = Math.round(node.attrs.modified / 12); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; - return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; + const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : ""; + return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0]; } }, // the id of the user who entered the text @@ -292,7 +290,7 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: false, toDOM(node: any) { const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; + return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0]; } }, -- cgit v1.2.3-70-g09d2 From 00e853027010db1ce9ccf0ed041ecb74c6529b53 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 23 Jul 2020 09:44:59 -0400 Subject: cleaned up contextMenus a bit --- src/client/views/ContextMenu.scss | 16 ++--- src/client/views/ContextMenuItem.tsx | 4 +- src/client/views/InkingStroke.tsx | 7 +- src/client/views/collections/CollectionView.tsx | 14 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 82 ++++++++++++---------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 7 +- src/client/views/nodes/DocumentView.tsx | 8 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 59 +++++++--------- 8 files changed, 100 insertions(+), 97 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 1bf242d93..86e0a568a 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -42,7 +42,7 @@ .contextMenu-item { // width: 11vw; //10vw - height: 30px; //2vh + height: 25px; //2vh background: whitesmoke; display: flex; //comment out to allow search icon to be inline with search text justify-content: left; @@ -70,9 +70,6 @@ text-align: center; font-size: 20px; margin-left: 5px; - margin-top: 5px; - margin-bottom: 5px; - height: 20px; } } .contextMenu-description { @@ -90,14 +87,11 @@ border-style: none; // padding: 10px 0px 10px 0px; white-space: nowrap; - font-size: 13px; + font-size: 10px; color: grey; - letter-spacing: 2px; + letter-spacing: 1px; text-transform: uppercase; padding-right: 30px; - margin-top: 5px; - height: 20px; - margin-bottom: 5px; } .contextMenu-item:hover { @@ -138,6 +132,10 @@ padding-left: 5px; } +.contextMenu-inlineMenu { + border-top: solid 1px; +} + .contextMenu-item:hover { transition: all 0.1s ease; background: $lighter-alt-accent; diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 68ebd8e14..86d1f22e1 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -98,9 +98,9 @@ export class ContextMenuItem extends React.Component )}
; if (!("noexpand" in this.props)) { - return <> + return
{this._items.map(prop => )} - ; +
; } return (
{ const cm = ContextMenu.Instance; if (cm) { - cm.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" }); + !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" }); - cm.addItem({ description: "Format Shape", event: this.formatShape, icon: "paint-brush" }); + cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" }); } }} > diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 9b04deff5..53fd83f26 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -305,18 +305,18 @@ export class CollectionView extends Touchable this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); + const options = cm.findByDescription("Options..."); + const optionItems = options && "subitems" in options ? options.subitems : []; + optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); if (this.props.Document.childLayout instanceof Doc) { - layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" }); + optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" }); } if (this.props.Document.childClickedOpenTemplateView instanceof Doc) { - layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" }); + optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" }); } - !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); + !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); - !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" }); + !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" }); const existingOnClick = cm.findByDescription("OnClick..."); const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index dc32ecb07..3b19a2ab8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1240,21 +1240,25 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); - appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); - appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); - appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); + appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); + appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + const viewctrls = ContextMenu.Instance.findByDescription("View Controls..."); + const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; + viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }); + viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); + !viewctrls && ContextMenu.Instance.addItem({ description: "View Controls...", subitems: viewCtrlItems, icon: "eye" }); + const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !this.props.isAnnotationOverlay && + !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode && optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye }); this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); - optionItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " snap lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }); optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" }); - optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); + appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); @@ -1263,40 +1267,42 @@ export class CollectionFreeFormView extends CollectionSubView { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".zip"; - input.onchange = async _e => { - const upload = Utils.prepend("/uploadDoc"); - const formData = new FormData(); - const file = input.files && input.files[0]; - if (file) { - formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json !== "error") { - const doc = await DocServer.GetRefField(json); - if (doc instanceof Doc) { - const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(doc); - setTimeout(() => { - SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { - docs.docs.forEach(d => LinkManager.Instance.addLink(d)); - }) - }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. - } - } + moreItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) }); + !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); + } + + importDocument = (x: number, y: number) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".zip"; + input.onchange = async _e => { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + const file = input.files && input.files[0]; + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc) { + const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument?.(doc); + setTimeout(() => { + SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { + docs.docs.forEach(d => LinkManager.Instance.addLink(d)); + }) + }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. } - }; - input.click(); + } } - }); - !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); + }; + input.click(); } + + @observable showTimeline = false; intersectRect(r1: { left: number, top: number, width: number, height: number }, diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index d79e2c9ff..ce39c3735 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -15,6 +15,7 @@ import { numberRange } from "../../../Utils"; import { ComputedField } from "../../../fields/ScriptField"; import { listSpec } from "../../../fields/Schema"; import { DocumentType } from "../../documents/DocumentTypes"; +import { InkingStroke } from "../InkingStroke"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; @@ -37,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu this.props.contextMenuItems?.().forEach(item => cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" })); + const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); + const appearance = cm.findByDescription("Appearance..."); + const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : []; + templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); + !appearance && cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "compass" }); + const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e703a81e2..38fa66d65 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -470,9 +470,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; - const appearance = ContextMenu.Instance.findByDescription("Appearance..."); - const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; - const changeItems: ContextMenuProps[] = []; const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); DocListCast(noteTypesDoc?.data).forEach(note => { @@ -484,24 +481,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); }); changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); - appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); - const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); - uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); - uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); - !Doc.UserDoc().noviceMode && uicontrols.push({ - description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => - proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" - }); - - appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" }); - this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - - !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); - - const funcs: ContextMenuProps[] = []; - const highlighting: ContextMenuProps[] = []; ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => highlighting.push({ @@ -515,8 +494,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.updateHighlights(); }, icon: "expand-arrows-alt" })); - funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); - funcs.push({ + + + const uicontrols: ContextMenuProps[] = []; + uicontrols.push({ description: `${this.layoutDoc._showSidebar ? "Hide" : "Show"} Sidebar`, event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); + uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); + uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); + !Doc.UserDoc().noviceMode && uicontrols.push({ + description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => + proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" + }); + cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); + + const appearance = cm.findByDescription("Appearance..."); + const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; + appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); + this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); + Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + appearanceItems.push({ description: "Convert to be a template style", event: () => { if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); @@ -541,11 +536,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); - funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - - funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + + const options = cm.findByDescription("Options..."); + const optionItems = options && "subitems" in options ? options.subitems : []; + !Doc.UserDoc().noviceMode && optionItems.push({ description: this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); + optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); + optionItems.push({ description: `${!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); + !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); this._downX = this._downY = Number.NaN; } @@ -562,11 +560,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; - @action - toggleMenubar = () => { - this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled"; - } - recordBullet = async () => { const completedCue = "end session"; const results = await DictationManager.Controls.listen({ -- cgit v1.2.3-70-g09d2