From a22f9b3c98c11d1170d1b0548bab61fc73fbc44f Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 23 Jul 2020 22:20:49 +0530 Subject: groups bugfix --- src/client/util/GroupManager.tsx | 8 ++++++++ src/client/views/DocumentDecorations.scss | 5 +++-- src/client/views/DocumentDecorations.tsx | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 2e5ecc543..a679c9087 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -48,6 +48,14 @@ export default class GroupManager extends React.Component<{}> { componentDidMount() { this.populateUsers(); + DocListCastAsync(this.GroupManagerDoc?.data).then(groups => { + groups?.forEach(group => { + const members: string[] = JSON.parse(StrCast(group.members)); + if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); + }); + + setGroups(this.currentUserGroups); + }); } /** diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 5948ada88..424a06431 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -157,6 +157,7 @@ $linkGap : 3px; grid-column-end: 2; pointer-events: all; padding-left: 5px; + cursor: pointer; } .documentDecorations-title { @@ -204,7 +205,7 @@ $linkGap : 3px; width: 20px; } -.documentDecorations-closeButton { +.documentDecorations-openInTab { opacity: 1; grid-column-start: 4; grid-column-end: 5; @@ -216,7 +217,7 @@ $linkGap : 3px; margin-top: auto; } -.documentDecorations-minimizeButton { +.documentDecorations-closeButton { opacity: 1; grid-column-start: 1; grid-column-end: 3; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index fec4ad9e0..46411c05b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -549,8 +549,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
) : ( -
Iconify
} placement="top"> -
+
Delete
} placement="top"> +
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
); @@ -616,7 +616,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
{"_"}
} -
Open Document In Tab
} placement="top">
+
Open Document In Tab
} placement="top">
{SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
Date: Thu, 23 Jul 2020 22:24:23 +0530 Subject: groups bugfix --- src/client/util/GroupManager.tsx | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index a679c9087..7d02b1e66 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -48,14 +48,6 @@ export default class GroupManager extends React.Component<{}> { componentDidMount() { this.populateUsers(); - DocListCastAsync(this.GroupManagerDoc?.data).then(groups => { - groups?.forEach(group => { - const members: string[] = JSON.parse(StrCast(group.members)); - if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); - }); - - setGroups(this.currentUserGroups); - }); } /** @@ -82,6 +74,17 @@ export default class GroupManager extends React.Component<{}> { return Promise.all(evaluating); } + populateGroups = () => { + DocListCastAsync(this.GroupManagerDoc?.data).then(groups => { + groups?.forEach(group => { + const members: string[] = JSON.parse(StrCast(group.members)); + if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); + }); + + setGroups(this.currentUserGroups); + }); + } + /** * @returns the options to be rendered in the dropdown menu to add users and create a group. */ @@ -97,14 +100,7 @@ export default class GroupManager extends React.Component<{}> { SelectionManager.DeselectAll(); this.isOpen = true; this.populateUsers(); - DocListCastAsync(this.GroupManagerDoc?.data).then(groups => { - groups?.forEach(group => { - const members: string[] = JSON.parse(StrCast(group.members)); - if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); - }); - - setGroups(this.currentUserGroups); - }); + this.populateGroups(); } /** -- cgit v1.2.3-70-g09d2 From 17ccb0a1a37c906c0151d3da5e92de3d94205924 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Fri, 24 Jul 2020 01:45:05 +0530 Subject: groups and acl bugfix --- src/client/util/GroupManager.tsx | 1 + src/fields/util.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 7d02b1e66..a727d21d0 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -48,6 +48,7 @@ export default class GroupManager extends React.Component<{}> { componentDidMount() { this.populateUsers(); + this.populateGroups(); } /** diff --git a/src/fields/util.ts b/src/fields/util.ts index 20b072786..cd1e24826 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -210,7 +210,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an return true; } - if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; + if (typeof prop === "string" && prop.startsWith("ACL") && ((target.author && Doc.CurrentUserEmail !== target.author) || !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value))) return true; + // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { -- cgit v1.2.3-70-g09d2 From 2f49497e9d74eda97b2327ca4dbcb0a11ac6c6c9 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Fri, 24 Jul 2020 17:11:09 +0530 Subject: added admin acl + renamed LinkCreatedBox to TaskCompletedBox and added custom text functionality + popups on sharing docs and creating groups + relocated SharingPermissions --- src/client/util/GroupManager.tsx | 15 +++++++- src/client/util/SharingManager.tsx | 46 +++++++++++++++---------- src/client/views/DocComponent.tsx | 3 +- src/client/views/MainView.tsx | 4 +-- src/client/views/collections/CollectionView.tsx | 8 ++--- src/client/views/nodes/DocumentLinksButton.tsx | 18 +++++----- src/client/views/nodes/DocumentView.tsx | 17 +++++---- src/client/views/nodes/LinkCreatedBox.scss | 21 ----------- src/client/views/nodes/LinkCreatedBox.tsx | 31 ----------------- src/client/views/nodes/LinkDescriptionPopup.tsx | 4 +-- src/client/views/nodes/TaskCompletedBox.scss | 20 +++++++++++ src/client/views/nodes/TaskCompletedBox.tsx | 32 +++++++++++++++++ src/fields/Doc.ts | 7 ++-- src/fields/util.ts | 28 +++++++++------ 14 files changed, 143 insertions(+), 111 deletions(-) delete mode 100644 src/client/views/nodes/LinkCreatedBox.scss delete mode 100644 src/client/views/nodes/LinkCreatedBox.tsx create mode 100644 src/client/views/nodes/TaskCompletedBox.scss create mode 100644 src/client/views/nodes/TaskCompletedBox.tsx (limited to 'src') diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index a727d21d0..72fba5c1b 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -16,6 +16,7 @@ import { StrCast, Cast } from "../../fields/Types"; import GroupMemberView from "./GroupMemberView"; import { setGroups } from "../../fields/util"; import { DocServer } from "../DocServer"; +import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle); @@ -36,11 +37,13 @@ export default class GroupManager extends React.Component<{}> { @observable currentGroup: Opt; // the currently selected group. @observable private createGroupModalOpen: boolean = false; private inputRef: React.RefObject = React.createRef(); // the ref for the input box. + private createGroupButtonRef: React.RefObject = React.createRef(); private currentUserGroups: string[] = []; @observable private buttonColour: "#979797" | "black" = "#979797"; @observable private groupSort: "ascending" | "descending" | "none" = "none"; + constructor(props: Readonly<{}>) { super(props); GroupManager.Instance = this; @@ -303,6 +306,14 @@ export default class GroupManager extends React.Component<{}> { this.selectedUsers = null; this.inputRef.current.value = ""; this.buttonColour = "#979797"; + + const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect(); + TaskCompletionBox.popupX = left - 2 * width; + TaskCompletionBox.popupY = top; + TaskCompletionBox.textDisplayed = "Group created!"; + TaskCompletionBox.taskCompleted = true; + setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); + } private get groupCreationModal() { @@ -345,7 +356,9 @@ export default class GroupManager extends React.Component<{}> { }) }} /> -
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 95c1bcda8..00d570670 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -7,8 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { List } from '../../fields/List'; import { DateField } from '../../fields/DateField'; import { ScriptField } from '../../fields/ScriptField'; -import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util'; -import { SharingPermissions } from '../util/SharingManager'; +import { GetEffectiveAcl, getPlaygroundMode, SharingPermissions } from '../../fields/util'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 225fb2e8e..81f2feee0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -61,7 +61,7 @@ import { DocumentManager } from '../util/DocumentManager'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { LinkMenu } from './linking/LinkMenu'; import { LinkDocPreview } from './nodes/LinkDocPreview'; -import { LinkCreatedBox } from './nodes/LinkCreatedBox'; +import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane"; import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager'; @@ -623,7 +623,7 @@ export class MainView extends React.Component { {this.mainContent} - + diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 53fd83f26..4848508be 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, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc'; +import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate, AclAdmin } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; @@ -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, getPlaygroundMode, distributeAcls } from '../../../fields/util'; +import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } 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'; @@ -48,7 +48,6 @@ import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; import CollectionMenu from './CollectionMenu'; -import { SharingPermissions } from '../../util/SharingManager'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -111,7 +110,8 @@ export class CollectionView extends Touchable { if (linkDoc) { - LinkCreatedBox.popupX = e.screenX; - LinkCreatedBox.popupY = e.screenY - 133; - LinkCreatedBox.linkCreated = true; + TaskCompletionBox.popupX = e.screenX; + TaskCompletionBox.popupY = e.screenY - 133; + TaskCompletionBox.taskCompleted = true; LinkDescriptionPopup.popupX = e.screenX; LinkDescriptionPopup.popupY = e.screenY - 100; LinkDescriptionPopup.descriptionPopup = true; - setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500); + setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500); } }); @@ -134,9 +134,9 @@ export class DocumentLinksButton extends React.Component { if (linkDoc) { - LinkCreatedBox.popupX = e.screenX; - LinkCreatedBox.popupY = e.screenY - 133; - LinkCreatedBox.linkCreated = true; + TaskCompletionBox.popupX = e.screenX; + TaskCompletionBox.popupY = e.screenY - 133; + TaskCompletionBox.taskCompleted = true; if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) { LinkDescriptionPopup.popupX = e.screenX; @@ -144,7 +144,7 @@ export class DocumentLinksButton extends React.Component { LinkCreatedBox.linkCreated = false; }), 2500); + setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500); } }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9e492650b..1d4b61545 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema"; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types"; -import { TraceMobx, GetEffectiveAcl } from '../../../fields/util'; +import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; @@ -25,7 +25,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { Scripting } from '../../util/Scripting'; import { SearchUtil } from '../../util/SearchUtil'; import { SelectionManager } from "../../util/SelectionManager"; -import SharingManager, { SharingPermissions } from '../../util/SharingManager'; +import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { CollectionView, CollectionViewType } from '../collections/CollectionView'; @@ -41,7 +41,7 @@ import { RadialMenu } from './RadialMenu'; import React = require("react"); import { DocumentLinksButton } from './DocumentLinksButton'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { LinkCreatedBox } from './LinkCreatedBox'; +import { TaskCompletionBox } from './TaskCompletedBox'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { LinkManager } from '../../util/LinkManager'; @@ -629,15 +629,15 @@ export class DocumentView extends DocComponent(Docu const makeLink = action((linkDoc: Doc) => { LinkManager.currentLink = linkDoc; - LinkCreatedBox.popupX = de.x; - LinkCreatedBox.popupY = de.y - 33; - LinkCreatedBox.linkCreated = true; + TaskCompletionBox.popupX = de.x; + TaskCompletionBox.popupY = de.y - 33; + TaskCompletionBox.taskCompleted = true; LinkDescriptionPopup.popupX = de.x; LinkDescriptionPopup.popupY = de.y; LinkDescriptionPopup.descriptionPopup = true; - setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500); + setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); }); if (de.complete.annoDragData) { /// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner @@ -818,6 +818,9 @@ export class DocumentView extends DocComponent(Docu !Doc.UserDoc().novice && 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...", noexpand: true, subitems: helpItems, icon: "question" }); + // to be removed for baseline + cm.addItem({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + // const existingAcls = cm.findByDescription("Privacy..."); // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" }); diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/LinkCreatedBox.scss deleted file mode 100644 index 3cbd38b55..000000000 --- a/src/client/views/nodes/LinkCreatedBox.scss +++ /dev/null @@ -1,21 +0,0 @@ -.linkCreatedBox-fade { - border: 1px solid rgb(100, 100, 100); - - - width: auto; - position: absolute; - - height: auto; - z-index: 10000; - border-radius: 13px; - font-size: 13px; - white-space: nowrap; - - color: rgb(100, 100, 100); - background-color: rgba(250, 250, 250, 0.85); - padding-top: 6.5px; - padding-bottom: 6.5px; - font-weight: bold; - padding-left: 9px; - padding-right: 9px; -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx deleted file mode 100644 index 648ae23c8..000000000 --- a/src/client/views/nodes/LinkCreatedBox.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { makeInterface } from "../../../fields/Schema"; -import "./LinkCreatedBox.scss"; -import { observable, action } from "mobx"; -import { Fade } from "@material-ui/core"; - - -@observer -export class LinkCreatedBox extends React.Component<{}> { - - @observable public static linkCreated: boolean = false; - @observable public static popupX: number = 500; - @observable public static popupY: number = 150; - - @action - public static changeLinkCreated = () => { - LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated; - } - - render() { - return -
Link Created
-
; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 06e8d30d1..d8fe47f4e 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -4,7 +4,7 @@ import "./LinkDescriptionPopup.scss"; import { observable, action } from "mobx"; import { EditableView } from "../EditableView"; import { LinkManager } from "../../util/LinkManager"; -import { LinkCreatedBox } from "./LinkCreatedBox"; +import { TaskCompletionBox } from "./TaskCompletedBox"; @observer @@ -31,7 +31,7 @@ export class LinkDescriptionPopup extends React.Component<{}> { onClick = (e: PointerEvent) => { if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) { LinkDescriptionPopup.descriptionPopup = false; - LinkCreatedBox.linkCreated = false; + TaskCompletionBox.taskCompleted = false; } } diff --git a/src/client/views/nodes/TaskCompletedBox.scss b/src/client/views/nodes/TaskCompletedBox.scss new file mode 100644 index 000000000..80b750b39 --- /dev/null +++ b/src/client/views/nodes/TaskCompletedBox.scss @@ -0,0 +1,20 @@ +.taskCompletedBox-fade { + border: 1px solid rgb(100, 100, 100); + + width: auto; + position: absolute; + + height: auto; + z-index: 10000; + border-radius: 13px; + font-size: 13px; + white-space: nowrap; + + color: rgb(100, 100, 100); + background-color: rgba(250, 250, 250, 0.85); + padding-top: 6.5px; + padding-bottom: 6.5px; + font-weight: bold; + padding-left: 9px; + padding-right: 9px; +} \ No newline at end of file diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx new file mode 100644 index 000000000..1ba2d1713 --- /dev/null +++ b/src/client/views/nodes/TaskCompletedBox.tsx @@ -0,0 +1,32 @@ +import React = require("react"); +import { observer } from "mobx-react"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface } from "../../../fields/Schema"; +import "./TaskCompletedBox.scss"; +import { observable, action } from "mobx"; +import { Fade } from "@material-ui/core"; + + +@observer +export class TaskCompletionBox extends React.Component<{}> { + + @observable public static taskCompleted: boolean = false; + @observable public static popupX: number = 500; + @observable public static popupY: number = 150; + @observable public static textDisplayed: string = "Link Created"; + + @action + public static toggleTaskCompleted = () => { + TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted; + } + + render() { + return +
{TaskCompletionBox.textDisplayed}
+
; + } +} \ No newline at end of file diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 917a6853c..0c816461c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -19,9 +19,8 @@ import { DateField } from "./DateField"; 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, SharingPermissions } from "./util"; import { LinkManager } from "../client/util/LinkManager"; -import { SharingPermissions } from "../client/util/SharingManager"; import JSZip = require("jszip"); import { saveAs } from "file-saver"; @@ -103,6 +102,7 @@ export const AclPrivate = Symbol("AclOwnerOnly"); export const AclReadonly = Symbol("AclReadOnly"); export const AclAddonly = Symbol("AclAddonly"); export const AclEdit = Symbol("AclEdit"); +export const AclAdmin = Symbol("AclAdmin"); export const UpdatingFromServer = Symbol("UpdatingFromServer"); const CachedUpdates = Symbol("Cached updates"); @@ -110,7 +110,8 @@ const AclMap = new Map([ [SharingPermissions.None, AclPrivate], [SharingPermissions.View, AclReadonly], [SharingPermissions.Add, AclAddonly], - [SharingPermissions.Edit, AclEdit] + [SharingPermissions.Edit, AclEdit], + [SharingPermissions.Admin, AclAdmin] ]); export function fetchProto(doc: Doc) { diff --git a/src/fields/util.ts b/src/fields/util.ts index cd1e24826..eba398dfb 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast, AclAdmin } 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, StrCast } from "./Types"; -import { SharingPermissions } from "../client/util/SharingManager"; function _readOnlySetter(): never { @@ -127,12 +126,20 @@ export function setGroups(groups: string[]) { currentUserGroups = groups; } +export enum SharingPermissions { + Admin = "Admin", + Edit = "Can Edit", + Add = "Can Add", + View = "Can View", + None = "Not Shared" +} + export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit; if (target[AclSym] && Object.keys(target[AclSym]).length) { - if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit; + if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin; if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; @@ -143,7 +150,8 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number) [AclPrivate, 0], [AclReadonly, 1], [AclAddonly, 2], - [AclEdit, 3] + [AclEdit, 3], + [AclAdmin, 4] ]); for (const [key, value] of Object.entries(target[AclSym])) { @@ -157,7 +165,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number) } return aclPresent ? effectiveAcl : AclEdit; } - return AclEdit; + return AclAdmin; } export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) { @@ -166,7 +174,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc ["Not Shared", 0], ["Can View", 1], ["Can Add", 2], - ["Can Edit", 3] + ["Can Edit", 3], + ["Admin", 4] ]); const dataDoc = target[DataSym]; @@ -206,11 +215,10 @@ 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) { - return true; - } + const effectiveAcl = GetEffectiveAcl(target, in_prop); + if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true; - if (typeof prop === "string" && prop.startsWith("ACL") && ((target.author && Doc.CurrentUserEmail !== target.author) || !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value))) return true; + if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { -- cgit v1.2.3-70-g09d2 From 150d1b3ea263dd0d6d8189d8bf6fd87c5505cfc4 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Fri, 24 Jul 2020 19:15:52 +0530 Subject: fixed instances of AclEdit to include AclAdmin --- src/client/views/DocComponent.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 5 +++-- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 5 +++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 +++-- src/fields/util.ts | 7 +++++-- 6 files changed, 17 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 00d570670..cfabae8a1 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -150,7 +150,7 @@ export function ViewBoxAnnotatableComponent

{ - if (GetEffectiveAcl(this.props.Document) === AclEdit || getPlaygroundMode()) { + const effectiveAcl = GetEffectiveAcl(this.props.Document); + if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || getPlaygroundMode()) { 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 84719b2c9..abe9ccd5e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,6 +1,6 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc"; +import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc"; import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; @@ -281,7 +281,7 @@ export class MarqueeView extends React.Component(Docu } 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" }); + const effectiveAcl = GetEffectiveAcl(this.props.Document); + (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && 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...")!); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 38fa66d65..2ac53ce6a 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, AclEdit } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -233,7 +233,8 @@ 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()); let unchanged = true; - if (GetEffectiveAcl(this.dataDoc) === AclEdit) { + const effectiveAcl = GetEffectiveAcl(this.dataDoc); + if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { 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/util.ts b/src/fields/util.ts index eba398dfb..a8b284658 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -67,11 +67,14 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number delete curValue[Parent]; delete curValue[OnUpdate]; } + + const effectiveAcl = GetEffectiveAcl(target); + const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); - const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly); - const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode; + const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly); + const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode; if (writeToDoc) { if (value === undefined) { -- cgit v1.2.3-70-g09d2 From 0546300e7775395e5787452a1665d4e47f0ba086 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Fri, 24 Jul 2020 20:15:33 +0530 Subject: link created popup issue fixed --- src/client/views/nodes/DocumentLinksButton.tsx | 2 ++ src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/TaskCompletedBox.tsx | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index e8127f768..c9d23ff3a 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -99,6 +99,7 @@ export class DocumentLinksButton extends React.Component { if (linkDoc) { + TaskCompletionBox.textDisplayed = "Link Created"; TaskCompletionBox.popupX = e.screenX; TaskCompletionBox.popupY = e.screenY - 133; TaskCompletionBox.taskCompleted = true; @@ -132,6 +133,7 @@ export class DocumentLinksButton extends React.Component { if (linkDoc) { + TaskCompletionBox.textDisplayed = "Link Created"; TaskCompletionBox.popupX = e.screenX; TaskCompletionBox.popupY = e.screenY - 133; TaskCompletionBox.taskCompleted = true; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f8df3fc38..a56943f6f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -629,6 +629,7 @@ export class DocumentView extends DocComponent(Docu const makeLink = action((linkDoc: Doc) => { LinkManager.currentLink = linkDoc; + TaskCompletionBox.textDisplayed = "Link Created"; TaskCompletionBox.popupX = de.x; TaskCompletionBox.popupY = de.y - 33; TaskCompletionBox.taskCompleted = true; diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx index 1ba2d1713..89602f219 100644 --- a/src/client/views/nodes/TaskCompletedBox.tsx +++ b/src/client/views/nodes/TaskCompletedBox.tsx @@ -13,7 +13,7 @@ export class TaskCompletionBox extends React.Component<{}> { @observable public static taskCompleted: boolean = false; @observable public static popupX: number = 500; @observable public static popupY: number = 150; - @observable public static textDisplayed: string = "Link Created"; + @observable public static textDisplayed: string; @action public static toggleTaskCompleted = () => { -- cgit v1.2.3-70-g09d2 From 760c51c493ef190aa5283944b083b55f7c057e6c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 24 Jul 2020 23:59:09 -0400 Subject: several siggnificant changes to how acls work with makin documents updated interactively when permissions chane. --- src/client/DocServer.ts | 80 +++++++++++++--------- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/Doc.ts | 48 +++++++++---- src/fields/Schema.ts | 2 +- src/fields/util.ts | 10 ++- 6 files changed, 95 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index bac324c77..8ded43468 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,13 +1,14 @@ import * as io from 'socket.io-client'; import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; -import { Opt, Doc, fetchProto } from '../fields/Doc'; +import { Opt, Doc, fetchProto, FieldsSym } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../fields/RefField'; -import { Id, HandleUpdate } from '../fields/FieldSymbols'; +import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols'; import GestureOverlay from './views/GestureOverlay'; import MobileInkOverlay from '../mobile/MobileInkOverlay'; import { runInAction } from 'mobx'; +import { ObjectField } from '../fields/ObjectField'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -207,12 +208,12 @@ export namespace DocServer { * the server if the document has not been cached. * @param id the id of the requested document */ - const _GetRefFieldImpl = (id: string): Promise> => { + const _GetRefFieldImpl = (id: string, force: boolean = false): Promise> => { // an initial pass through the cache to determine whether the document needs to be fetched, // is already in the process of being fetched or already exists in the // cache const cached = _cache[id]; - if (cached === undefined) { + if (cached === undefined || force) { // NOT CACHED => we'll have to send a request to the server // synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) @@ -226,7 +227,16 @@ export namespace DocServer { const deserializeField = getSerializedField.then(async fieldJson => { // deserialize const field = await SerializationHelper.Deserialize(fieldJson); - if (field !== undefined) { + if (force && field instanceof Doc && cached instanceof Doc) { + Array.from(Object.keys(field)).forEach(key => { + const fieldval = field[key]; + if (fieldval instanceof ObjectField) { + fieldval[Parent] = undefined; + } + cached[key] = field[key]; + }); + } + else if (field !== undefined) { _cache[id] = field; } else { delete _cache[id]; @@ -238,8 +248,8 @@ export namespace DocServer { }); // here, indicate that the document associated with this id is currently // being retrieved and cached - _cache[id] = deserializeField; - return deserializeField; + !force && (_cache[id] = deserializeField); + return force ? cached as any : deserializeField; } else if (cached instanceof Promise) { // BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s), // and requested the document I'm looking for. Shouldn't fetch again, just @@ -260,11 +270,11 @@ export namespace DocServer { } }; - let _GetRefField: (id: string) => Promise> = errorFunc; + let _GetRefField: (id: string, force: boolean) => Promise> = errorFunc; let _GetCachedRefField: (id: string) => Opt = errorFunc; - export function GetRefField(id: string): Promise> { - return _GetRefField(id); + export function GetRefField(id: string, force = false): Promise> { + return _GetRefField(id, force); } export function GetCachedRefField(id: string): Opt { return _GetCachedRefField(id); @@ -330,29 +340,35 @@ export namespace DocServer { const proms: Promise[] = []; runInAction(() => { for (const field of fields) { - if (field !== undefined && field !== null) { + if (field !== undefined && field !== null && !_cache[field.id]) { // deserialize - const prom = SerializationHelper.Deserialize(field).then(deserialized => { - fieldMap[field.id] = deserialized; - - //overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (deserialized !== undefined) { - _cache[field.id] = deserialized; - } else { - delete _cache[field.id]; - } - return deserialized; - }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - _cache[field.id] = prom; - // adds to a list of promises that will be awaited asynchronously - proms.push(prom); + const cached = _cache[field.id]; + if (!cached) { + const prom = SerializationHelper.Deserialize(field).then(deserialized => { + fieldMap[field.id] = deserialized; + + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; + } else { + delete _cache[field.id]; + } + return deserialized; + }); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + _cache[field.id] = prom; + + // adds to a list of promises that will be awaited asynchronously + proms.push(prom); + } else if (cached instanceof Promise) { + proms.push(cached as any); + } } } }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 979b21321..38c1edde2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -944,7 +944,8 @@ export class CollectionFreeFormView extends CollectionSubView this.props.active(outsideReaction) || this.backgroundActive ? true : false; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { - ...this.props, + pinToPres: this.props.pinToPres, + whenActiveChanged: this.props.whenActiveChanged, NativeHeight: returnZero, NativeWidth: returnZero, fitToBox: false, @@ -1145,7 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView this.doLayoutComputation, + this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation }, (elements) => this._layoutElements = elements || [], { fireImmediately: true, name: "doLayout" }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2ac53ce6a..53234d819 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -690,7 +690,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ); this._disposers.editorState = reaction( () => { - if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) { + if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) { return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; } return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c9231753f..9020dcc84 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -104,7 +104,7 @@ export const AclAddonly = Symbol("AclAddonly"); export const AclEdit = Symbol("AclEdit"); export const AclAdmin = Symbol("AclAdmin"); export const UpdatingFromServer = Symbol("UpdatingFromServer"); -const CachedUpdates = Symbol("Cached updates"); +export const CachedUpdates = Symbol("Cached updates"); const AclMap = new Map([ [SharingPermissions.None, AclPrivate], @@ -115,15 +115,15 @@ const AclMap = new Map([ ]); export function fetchProto(doc: Doc) { - // if (doc.author !== Doc.CurrentUserEmail) { - 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).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); - if (Object.keys(permissions).length) doc[AclSym] = permissions; - }); - // } + if (Object.keys(permissions).length) doc[AclSym] = permissions; + + if (GetEffectiveAcl(doc) === AclPrivate) { + runInAction(() => doc[FieldsSym](true)); + } if (doc.proto instanceof Promise) { doc.proto.then(fetchProto); @@ -143,7 +143,7 @@ export class Doc extends RefField { 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.___fieldKeys); runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); return Object.keys(obj); }, @@ -151,11 +151,11 @@ export class Doc extends RefField { if (prop.toString() === "__LAYOUT__") { return Reflect.getOwnPropertyDescriptor(target, prop); } - if (prop in target.__fields) { + if (prop in target.__fieldKeys) { return { configurable: true,//TODO Should configurable be true? enumerable: true, - value: target.__fields[prop] + value: 0//() => target.__fields[prop]) }; } return Reflect.getOwnPropertyDescriptor(target, prop); @@ -179,15 +179,23 @@ export class Doc extends RefField { this.___fields = value; for (const key in value) { const field = value[key]; + field && (this.__fieldKeys[key] = true); if (!(field instanceof ObjectField)) continue; field[Parent] = this[Self]; field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); } } + private get __fieldKeys() { return this.___fieldKeys; } + private set __fieldKeys(value) { + this.___fieldKeys = value; + } @observable private ___fields: any = {}; + @observable + private ___fieldKeys: any = {}; + private [UpdatingFromServer]: boolean = false; private [Update] = (diff: any) => { @@ -196,7 +204,14 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; - public [FieldsSym] = () => this.___fields; + public [FieldsSym] = (clear?: boolean) => { + if (clear) { + this.___fields = {}; + this.___fieldKeys = {}; + } + return this.___fields; + } + @observable public [AclSym]: { [key: string]: symbol }; public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); @@ -237,11 +252,18 @@ export class Doc extends RefField { const fKey = key.substring(7); const fn = async () => { const value = await SerializationHelper.Deserialize(set[key]); + const prev = GetEffectiveAcl(this); this[UpdatingFromServer] = true; this[fKey] = value; + if (fKey.startsWith("ACL")) { + fetchProto(this); + } this[UpdatingFromServer] = false; + if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { + DocServer.GetRefField(this[Id], true); + } }; - if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { + if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; await fn(); } else { diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts index 98ef3e087..c6a8d6ae9 100644 --- a/src/fields/Schema.ts +++ b/src/fields/Schema.ts @@ -31,7 +31,7 @@ export function makeInterface(...schemas: T): InterfaceFu } const proto = new Proxy({}, { get(target: any, prop, receiver) { - const field = receiver.doc[prop]; + const field = receiver.doc?.[prop]; if (prop in schema) { const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should? if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec diff --git a/src/fields/util.ts b/src/fields/util.ts index 2b970d342..e0b1843a1 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,11 +1,11 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast, AclAdmin } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { action, trace } from "mobx"; -import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; +import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; import { ComputedField } from "./ScriptField"; import { ScriptCast, StrCast } from "./Types"; @@ -77,8 +77,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (writeToDoc) { if (value === undefined) { + delete target.__fieldKeys[prop]; delete target.__fields[prop]; } else { + target.__fieldKeys[prop] = true; target.__fields[prop] = value; } //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; @@ -137,7 +139,7 @@ export enum SharingPermissions { } export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { - if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit; + if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin; if (target[AclSym] && Object.keys(target[AclSym]).length) { @@ -241,6 +243,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { let prop = in_prop; + + if (in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop]; if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; if (prop === LayoutSym) { -- cgit v1.2.3-70-g09d2 From 99e0b01dbe41e56cfdb748b53f03fe71d73a80b7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 25 Jul 2020 10:54:12 -0400 Subject: pushpin annotations can follow links to multple targets at once. chaned text boxes so that they can be made isBackround .fixed addin/remoin documents from freeform collections. --- src/client/util/DocumentManager.ts | 44 +++++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/Doc.ts | 1 - 4 files changed, 27 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 51b50878d..523dbfca0 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -152,18 +152,18 @@ export class DocumentManager { const first = getFirstDocView(annotatedDoc); if (first) { annotatedDoc = first.props.Document; - if (docView) { - docView.props.focus(annotatedDoc, false); - } + docView?.props.focus(annotatedDoc, false); } } if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? - if (originatingDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden; + if (originatingDoc?.isPushpin) { + docView.props.Document.hidden = !docView.props.Document.hidden; + } else { docView.props.Document.hidden && (docView.props.Document.hidden = undefined); docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish); + highlight(); } - highlight(); } else { const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; @@ -218,25 +218,27 @@ export class DocumentManager { const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs); - const linkDoc = linkDocList.length && linkDocList[0]; - if (linkDoc) { - const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; - const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : - doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); - if (target) { - const containerDoc = (await Cast(target.annotationOn, Doc)) || target; - containerDoc.currentTimecode = targetTimecode; - const targetContext = await target?.context as Doc; - const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished); + const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : []; + followLinks.forEach(async linkDoc => { + if (linkDoc) { + const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; + const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : + doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") : + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); + if (target) { + const containerDoc = (await Cast(target.annotationOn, Doc)) || target; + containerDoc.currentTimecode = targetTimecode; + const targetContext = await target?.context as Doc; + const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished); + } else { + finished?.(); + } } else { finished?.(); } - } else { - finished?.(); - } + }); } } Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 38c1edde2..079add4b0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -944,6 +944,9 @@ export class CollectionFreeFormView extends CollectionSubView this.props.active(outsideReaction) || this.backgroundActive ? true : false; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { + addDocument: this.props.addDocument, + removeDocument: this.props.removeDocument, + moveDocument: this.props.moveDocument, pinToPres: this.props.pinToPres, whenActiveChanged: this.props.whenActiveChanged, NativeHeight: returnZero, diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 53234d819..966aaa895 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1406,7 +1406,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp

diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 9020dcc84..3ea53a9e0 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -916,7 +916,6 @@ export namespace Doc { return doc; } export function UnBrushDoc(doc: Doc) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.delete(doc); brushManager.BrushedDoc.delete(Doc.GetProto(doc)); -- cgit v1.2.3-70-g09d2 From c841982c0de733b49f22a6e0d04427c166aabcf1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 25 Jul 2020 14:57:41 -0400 Subject: added ability to resize fixed aspect docusments with ctrlKey. added menu item to hide linkButton --- src/client/views/DocumentDecorations.tsx | 14 ++++++++------ src/client/views/nodes/DocumentView.tsx | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 46411c05b..8d63537e7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -438,8 +438,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (nwidth / nheight !== width / height) { height = nheight / nwidth * width; } - if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; - else dW = dH * nwidth / nheight; + if (!e.ctrlKey) { + if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; + else dW = dH * nwidth / nheight; + } } const actualdW = Math.max(width + (dW * scale), 20); const actualdH = Math.max(height + (dH * scale), 20); @@ -463,20 +465,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else if (nwidth > 0 && nheight > 0) { if (Math.abs(dW) > Math.abs(dH)) { - if (!fixedAspect) { + if (!fixedAspect || e.ctrlKey) { doc._nativeWidth = actualdW / (doc._width || 1) * (doc._nativeWidth || 0); } doc._width = actualdW; if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width; - else doc._height = actualdH; + else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH; } else { - if (!fixedAspect) { + if (!fixedAspect || e.ctrlKey) { doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0); } doc._height = actualdH; if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height; - else doc._width = actualdW; + else if (!fixedAspect || !e.ctrlKey) doc._width = actualdW; } } else { dW && (doc._width = actualdW); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a56943f6f..8cadafccd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -758,6 +758,7 @@ export class DocumentView extends DocComponent(Docu 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" }); + appearanceItems.push({ description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" }); !appearance && cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "compass" }); const options = cm.findByDescription("Options..."); -- cgit v1.2.3-70-g09d2 From 5454385b6fabbcafee4d797829fb9ceaa8a71ed1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 25 Jul 2020 20:14:16 -0400 Subject: made buttons turn into fonticons on iconify. droppin fonticons onto linear views keeps them as buttons. droppin buttons on linear view makes them fonticons. fixed ColletionMenu scripts to work with templates by usin self instead of this. fixed pointer events with text boxes for filterview & normal. fixed fieldKey deletin and addin --- src/client/util/CurrentUserUtils.ts | 12 +++++++++-- src/client/util/DragManager.ts | 7 +++---- src/client/util/DropConverter.ts | 2 +- src/client/views/MainView.tsx | 4 ++-- src/client/views/collections/CollectionMenu.tsx | 23 ++++++++++++++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FontIconBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- src/fields/util.ts | 4 ++-- 9 files changed, 37 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b47cfb33a..6d752832a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -346,6 +346,14 @@ export class CurrentUserUtils { iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); } + if (doc["template-icon-view-button"] === undefined) { + const iconBtnView = Docs.Create.FontIconDocument({ + title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30, + _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + }); + iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON); + doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView); + } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") @@ -359,11 +367,11 @@ export class CurrentUserUtils { doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } if (doc["template-icons"] === undefined) { - doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, + doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 })); } else { const templateIconsDoc = Cast(doc["template-icons"], Doc, null); - const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, + const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; DocListCastAsync(templateIconsDoc.data).then(async curIcons => { await Promise.all(curIcons!); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 837f0b1db..4b1860b5c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -224,11 +224,10 @@ export namespace DragManager { } // drag a button template and drop a new button - export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { + export function + StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { const finishDrag = (e: DragCompleteEvent) => { - const bd = params.length > Object.keys(vars).length ? - Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }) : - Docs.Create.FontIconDocument({ toolTip: title, z: 1, _nativeWidth: 30, _nativeHeight: 30, _width: 30, _height: 30, title, onClick: ScriptField.MakeScript(script) }); + const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }); params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields initialize?.(bd); Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List(params); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index f9837298d..d0acf14c3 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -57,7 +57,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { data?.draggedDocuments.map((doc, i) => { let dbox = doc; // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant - if (doc.type === DocumentType.FONTICON) { + if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) { dbox = Doc.MakeAlias(doc); } else if (!doc.onDragStart && !doc.isButtonBar) { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 81f2feee0..ae3f05fb7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,7 +8,7 @@ import { faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, - faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap + faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser } from '@fortawesome/free-solid-svg-icons'; import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -150,7 +150,7 @@ export class MainView extends React.Component { faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, - faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap); + faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser); this.initEventListeners(); this.initAuthenticationRouters(); } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 992c1f600..81c349b26 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction, Lambda } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc"; import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types"; import AntimodeMenu from "../AntimodeMenu"; import "./CollectionMenu.scss"; @@ -24,6 +24,7 @@ import { Document } from "../../../fields/documentSchemas"; import { SelectionManager } from "../../util/SelectionManager"; import { DocumentView } from "../nodes/DocumentView"; import { ColorState } from "react-color"; +import { ObjectField } from "../../../fields/ObjectField"; @observer export default class CollectionMenu extends AntimodeMenu { @@ -72,42 +73,48 @@ export class CollectionViewBaseChrome extends React.Component source.length && (this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]))), initialize: emptyFunction, }; _narrativeCommand = { params: ["target", "source"], title: "child click view", - script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])", + script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])", immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))), initialize: emptyFunction, }; _contentCommand = { params: ["target", "source"], title: "clear content", - script: "getProto(this.target).data = copyField(this.source);", + script: "getProto(self.target).data = copyField(self.source);", immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List(source)), // Doc.aliasDocs(source), initialize: emptyFunction, }; _viewCommand = { params: ["target"], title: "bookmark view", - script: "this.target._panX = this['target-panX']; this.target._panY = this['target-panY']; this.target._viewScale = this['target-viewScale'];", + script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];", immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }), initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; }, }; _clusterCommand = { params: ["target"], title: "fit content", - script: "this.target._fitToBox = !this.target._fitToBox;", + script: "self.target._fitToBox = !self.target._fitToBox;", immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox), initialize: emptyFunction }; _fitContentCommand = { params: ["target"], title: "toggle clusters", - script: "this.target.useClusters = !this.target.useClusters;", + script: "self.target.useClusters = !self.target.useClusters;", immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters), initialize: emptyFunction }; + _saveFilterCommand = { + params: ["target"], title: "save filter", + script: "self.target._docFilters = copyField(self['target-docFilters']);", + immediate: undoBatch((source: Doc[]) => this.target._docFilters = undefined), + initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField && ObjectField.MakeCopy(this.target._docFilters as any as ObjectField); }, + }; - _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; + _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; _stacking_commands = [this._contentCommand, this._templateCommand]; _masonry_commands = [this._contentCommand, this._templateCommand]; _schema_commands = [this._templateCommand, this._narrativeCommand]; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8cadafccd..df4576243 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -294,7 +294,7 @@ export class DocumentView extends DocComponent(Docu let stopPropagate = true; let preventDefault = true; !this.props.Document.isBackground && this.props.bringToFront(this.props.Document); - if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click + if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (!(e.nativeEvent as any).formattedHandled) { if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself const func = () => this.onDoubleClickHandler.script.run({ diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index ab34e13b0..2611d2ca7 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -67,7 +67,7 @@ export class FontIconBox extends DocComponent( background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)), boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined }}> - + {!this.rootDoc.title ? (null) :
{StrCast(this.rootDoc.label, StrCast(this.rootDoc.title).substring(0, 6))}
} ; return !this.layoutDoc.toolTip ? button : diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 966aaa895..6b6fc5da2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1401,12 +1401,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onDoubleClick={this.onDoubleClick} >
diff --git a/src/fields/util.ts b/src/fields/util.ts index e0b1843a1..47da7d7bf 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -77,10 +77,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (writeToDoc) { if (value === undefined) { - delete target.__fieldKeys[prop]; + target.__fieldKeys && (delete target.__fieldKeys[prop]); delete target.__fields[prop]; } else { - target.__fieldKeys[prop] = true; + target.__fieldKeys && (target.__fieldKeys[prop] = true); target.__fields[prop] = value; } //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; -- cgit v1.2.3-70-g09d2