From a807da6b6fed067888f817e2273b29b8b6219e52 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 23 Jun 2023 12:05:24 -0400 Subject: fixed collection acls --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 77cb7f88c..430e8ecd5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl } from '../../../../fields/util'; +import { GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; import { intersectRect, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; @@ -390,7 +390,11 @@ export class MarqueeView extends React.Component (d.embedContainer = newCollection)); + newCollection['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + selected.forEach(d => { + d.embedContainer = newCollection; + d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + }); this.hideMarquee(); return newCollection; }); -- cgit v1.2.3-70-g09d2 From 5a9af979cc293d0e3843270ee053c24bf0eb6ef5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Jul 2023 16:15:27 -0400 Subject: changed acl inheritance for docking views. --- src/client/documents/Documents.ts | 10 ++-- src/client/util/LinkFollower.ts | 2 +- src/client/util/SharingManager.tsx | 70 ++++++++++------------ src/client/views/DocComponent.tsx | 30 +++++----- src/client/views/DocumentDecorations.tsx | 40 ++++++++----- .../views/collections/CollectionDockingView.tsx | 12 ++-- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 8 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 10 ++-- src/fields/Doc.ts | 11 +++- src/fields/util.ts | 33 +++++----- 15 files changed, 123 insertions(+), 113 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 06ff95df0..18e8b5940 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -859,7 +859,7 @@ export namespace Docs { // users placeholderDoc as proto if it exists const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); - + if (placeholderDoc) { dataDoc.proto = proto; } @@ -1053,7 +1053,7 @@ export namespace Docs { export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id); - documents.forEach(d => (d.embedContainer = inst)); + documents.forEach(d => Doc.SetContainer(d, inst)); return inst; } @@ -1158,7 +1158,9 @@ export namespace Docs { } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id); + const ret = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id); + documents.map(c => Doc.SetContainer(c, ret)); + return ret; } export function DirectoryImportDocument(options: DocumentOptions = {}) { @@ -1186,7 +1188,7 @@ export namespace Docs { options, id ); - configs.map(c => (c.doc.embedContainer = doc)); + configs.map(c => Doc.SetContainer(c.doc, doc)); return doc; } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index f74409e42..3e526c4c0 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -103,7 +103,7 @@ export class LinkFollower { Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target); movedTarget = true; } - target.embedContainer = sourceDocParent; + Doc.SetContainer(target, sourceDocParent); const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)]; if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) { target.x = moveTo[0]; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 534a9cfde..64b2bb5b8 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,12 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IconButton, Size } from 'browndash-components'; import { concat, intersection } from 'lodash'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import Select from 'react-select'; import * as RequestPromise from 'request-promise'; -import { Doc, DocCastAsync, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; +import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; import { AclAdmin, AclPrivate, AclUnset, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; @@ -24,7 +23,6 @@ import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; import { SelectionManager } from './SelectionManager'; import './SharingManager.scss'; -import { Docs } from '../documents/Documents'; export interface User { email: string; @@ -166,7 +164,7 @@ export class SharingManager extends React.Component<{}> { // or if the 'Override Nested' checkbox is selected var childDocs = DocListCast(target.data); childDocs.map(doc => { - if (this.overrideNested || doc[acl]==undefined){ + if (this.overrideNested || doc[acl] == undefined) { this.setInternalSharing(recipient, permission, doc); } }); @@ -207,7 +205,7 @@ export class SharingManager extends React.Component<{}> { // or if the 'Override Private' checkbox is selected var childDocs = DocListCast(target.data); childDocs.map(doc => { - if (this.overrideNested || doc[acl]==undefined){ + if (this.overrideNested || doc[acl] == undefined) { this.setInternalGroupSharing(group, permission, doc); } }); @@ -288,7 +286,7 @@ export class SharingManager extends React.Component<{}> { this.shareFromPropertiesSidebar(shareWith, permission, DocListCast(doc.data), layout); } if (GetEffectiveAcl(doc) === AclAdmin) { - if ( shareWith == 'Public' && layout) shareWith = 'Public-layout'; + if (shareWith == 'Public' && layout) shareWith = 'Public-layout'; distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard); } this.setDashboardBackground(doc, permission as SharingPermissions); @@ -538,13 +536,12 @@ export class SharingManager extends React.Component<{}> { const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(Doc.GetProto(targetDoc)[userKey])) : StrCast(targetDoc[userKey]); - if (this.layoutDocAcls){ + if (this.layoutDocAcls) { if (targetDoc[DocAcl][userKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; - else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); + else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[userKey]) : '-multiple-'; - } - else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; - + } else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + return !permissions ? null : (
{user.email} @@ -570,12 +567,11 @@ export class SharingManager extends React.Component<{}> { // the owner of the doc and the current user are placed at the top of the user list. const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`; var curUserPermission; - if (this.layoutDocAcls){ - if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; - else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); - else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]); - } - else curUserPermission = StrCast(targetDoc[userKey]); + if (this.layoutDocAcls) { + if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; + else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); + else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]); + } else curUserPermission = StrCast(targetDoc[userKey]); // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name userListContents.unshift( sameAuthor ? ( @@ -609,16 +605,15 @@ export class SharingManager extends React.Component<{}> { .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey])); // const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][groupKey] ? HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name : StrCast(Doc.GetProto(targetDoc)[groupKey])) : StrCast(targetDoc[groupKey]); - if (this.layoutDocAcls){ - if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout'; - if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name; - else{ - if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public'; - if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]); - else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-'; - } + if (this.layoutDocAcls) { + if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout'; + if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name; + else { + if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public'; + if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]); + else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-'; } - else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; + } else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : (
@@ -629,7 +624,7 @@ export class SharingManager extends React.Component<{}> {
) : null} -
+
{admin || this.myDocAcls ? ( {this.sharingOptions(true)} @@ -706,9 +700,9 @@ export class SharingManager extends React.Component<{}> {
) : ( -
-
-
+
+
+
(this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
@@ -717,7 +711,7 @@ export class SharingManager extends React.Component<{}> {
(this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> -
+
Individuals   {this.individualSort === 'ascending' ? ( @@ -732,7 +726,7 @@ export class SharingManager extends React.Component<{}> {
(this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> -
+
Groups  
GroupManager.Instance?.open())}> diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index f3aa8451a..1d0feec74 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -196,52 +196,52 @@ export function ViewBoxAnnotatableComponent

() aclKeys.forEach(key => added.forEach(d => { - if (key != 'acl-Me'){ - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]) - const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl - const permission = HierarchyMapping.get(permissionSymbol)!.name - distributeAcls(key, permission, Doc.GetProto(d)) + if (key != 'acl-Me') { + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); + const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; + const permission = HierarchyMapping.get(permissionSymbol)!.name; + distributeAcls(key, permission, Doc.GetProto(d)); } }) ); if (effectiveAcl === AclAugment) { added.map(doc => { - doc.embedContainer = this.props.Document; + Doc.SetContainer(doc, this.props.Document); if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); const parent = DocCast(doc.embedContainer); doc.embedContainer && inheritParentAcls(parent, doc); for (const key of Object.keys(parent)) { - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) - if (symbol && key.startsWith('acl')){ + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); + if (symbol && key.startsWith('acl')) { const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); } } }); - } else { - added + } else { + added .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) .map(doc => { // only make a pushpin if we have acl's to edit the document //DocUtils.LeavePushpin(doc); doc._dragOnlyWithinContainer = undefined; - doc.embedContainer = this.props.Document; + Doc.SetContainer(doc, this.props.Document); if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; const parent = DocCast(doc.embedContainer); doc.embedContainer && inheritParentAcls(parent, doc); for (const key of Object.keys(Doc.GetProto(parent))) { - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) - if (symbol && key.startsWith('acl')){ + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); + if (symbol && key.startsWith('acl')) { const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); } } - }); - + }); + const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add))); else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4454a3ec1..cdd9e62d8 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -201,9 +201,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { const first = SelectionManager.Views()[0]; const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc); - if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment){ + if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) { return false; - } + } const dragDocView = SelectionManager.Views()[0]; const containers = new Set(); SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer))); @@ -766,13 +766,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // sharing const acl = this.showLayoutAcl ? GetEffectiveLayoutAcl(seldocview.rootDoc) : GetEffectiveAcl(seldocview.rootDoc); - const docShareMode = HierarchyMapping.get(acl)!.name + const docShareMode = HierarchyMapping.get(acl)!.name; const shareMode = StrCast(docShareMode); var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image; // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; - const hideResizers = ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; + const hideResizers = + ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button @@ -826,26 +827,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const radiusHandleLocation = Math.min(radiusHandle, maxDist); const sharingMenu = docShareMode ? ( -

+
  {shareSymbolIcon + ' ' + shareMode}   - {!Doc.noviceMode ? -
-
- (this.showLayoutAcl = !this.showLayoutAcl))} /> + {!Doc.noviceMode ? ( +
+
+ (this.showLayoutAcl = !this.showLayoutAcl))} />
-
Layout
+
Layout
- : null } + ) : null}  
) : (
); - + const titleArea = this._editingTitle ? ( e.stopPropagation()} /> ) : ( -
{e.stopPropagation}}> - { hideTitle ? null : {this.selectionTitle}} +
{ + e.stopPropagation; + }}> + {hideTitle ? null : ( + + {this.selectionTitle} + + )} {sharingMenu} {!useLock ? null : ( toggle ability to interact with document
} placement="top"> @@ -872,7 +882,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P )}
); - + return (
Doc.MakeEmbedding(origtabdoc)); if (newtabdocs.length) { Doc.GetProto(newtab).data = new List(newtabdocs); - newtabdocs.forEach(ntab => (ntab.embedContainer = newtab)); + newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab)); } json = json.replace(origtab[Id], newtab[Id]); return newtab; @@ -497,11 +497,11 @@ export class CollectionDockingView extends CollectionSubView() { tabCreated = (tab: any) => { const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {}); aclKeys.forEach(key => { - if (key != 'acl-Me'){ - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]) - const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl - const permission = HierarchyMapping.get(permissionSymbol)!.name - distributeAcls(key, permission, Doc.GetProto(tab)) + if (key != 'acl-Me') { + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); + const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; + const permission = HierarchyMapping.get(permissionSymbol)!.name; + distributeAcls(key, permission, Doc.GetProto(tab)); } }); this.tabMap.add(tab); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4cd3885f5..d78a0e781 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -180,7 +180,7 @@ export class CollectionTreeView extends CollectionSubView (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before); - res && (doc.embedContainer = this.props.Document); + res && Doc.SetContainer(doc, this.props.Document); return res; }, true); if (this.doc.resolvedDataDoc instanceof Promise) return false; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4d780f46b..3473eee18 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -262,7 +262,7 @@ export class TabDocView extends React.Component { pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; pinDoc.presDuration = pinDoc.presDuration ?? 1000; pinDoc.groupWithUp = false; - pinDoc.embedContainer = curPres; + Doc.SetContainer(pinDoc, curPres); // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8d8d895c6..a2269075d 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -392,7 +392,7 @@ export class TreeView extends React.Component { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && (doc.embedContainer = this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -455,7 +455,7 @@ export class TreeView extends React.Component { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - dataIsComputed && (doc.embedContainer = this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -563,7 +563,7 @@ export class TreeView extends React.Component { } const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - !dataIsComputed && added && (doc.embedContainer = this.doc.embedContainer); + !dataIsComputed && added && Doc.SetContainer(doc, this.doc.embedContainer); return added; }; @@ -1192,7 +1192,7 @@ export class TreeView extends React.Component { TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeViewOpen = true; - child.embedContainer = treeView.Document; + Doc.SetContainer(child, treeView.Document); } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9b0abc48b..764b1e08a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -392,7 +392,7 @@ export class MarqueeView extends React.Component { - d.embedContainer = newCollection; + Doc.SetContainer(d, newCollection); d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; }); this.hideMarquee(); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 2290e0711..ca5ec9389 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -46,7 +46,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey)); - droppedDocs.lastElement().embedContainer = this.dataDoc; + Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place return added; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 202a9f851..e5ea8e0c1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -564,7 +564,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(schema: S, props: any, mapKey switch (GetEffectiveAcl(props.DataDoc)) { case AclAugment: const prevNode = state.selection.$cursor.nodeBefore; - const prevUser = prevNode.marks[prevNode.marks.length-1].attrs.userid - if (prevUser != Doc.CurrentUserEmail){ + const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + if (prevUser != Doc.CurrentUserEmail) { return false; } } @@ -263,7 +263,7 @@ export function buildKeymap>(schema: S, props: any, mapKey bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; - + if ( !deleteSelection(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); @@ -334,7 +334,7 @@ export function buildKeymap>(schema: S, props: any, mapKey //Command to create a blank space bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (GetEffectiveAcl(props.DataDoc)!=AclEdit && GetEffectiveAcl(props.DataDoc)!=AclAugment && GetEffectiveAcl(props.DataDoc)!=AclAdmin) return true; + if (GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); dispatch(splitMetadata(marks, state.tr)); return false; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 99712fb04..9c138d348 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -20,7 +20,6 @@ import { AclEdit, AclPrivate, AclReadonly, - AclSelfEdit, AclUnset, Animation, CachedUpdates, @@ -138,7 +137,7 @@ export const HierarchyMapping: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); +export const ReverseHierarchyMap: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); // caches the document access permissions for the current user. // this recursively updates all protos as well. @@ -478,6 +477,14 @@ export namespace Doc { // }); // } + export function SetContainer(doc: Doc, container: Doc) { + doc.embedContainer = container; + if (Doc.GetProto(container).author === doc.author) { + Object.keys(Doc.GetProto(container)) + .filter(key => key.startsWith('acl')) + .forEach(key => (doc[key] = Doc.GetProto(container)[key])); + } + } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; if (update) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 0f613d926..48de36efe 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -131,10 +131,10 @@ export function inheritParentAcls(parent: Doc, child: Doc) { for (const key of Object.keys(parent)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) - if (symbol){ + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); + if (symbol) { const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - key.startsWith('acl') && distributeAcls(key, sharePermission, child) + key.startsWith('acl') && distributeAcls(key, sharePermission, child); } } } @@ -171,7 +171,7 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { // return layout acl from cache or chache the acl and return. const getEffectiveLayoutAclCache = computedFn(function (target: any, user?: string) { return getEffectiveLayoutAcl(target, user); - }, true); +}, true); /** * Calculates the effective access right to a document for the current user. @@ -183,13 +183,13 @@ export function GetEffectiveAcl(target: any, user?: string): symbol { } /** -* Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set. -*/ + * Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set. + */ export function GetEffectiveLayoutAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; return getEffectiveLayoutAclCache(target, user); - } +} function getPropAcl(target: any, prop: string | symbol | number) { if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent @@ -234,9 +234,9 @@ function getEffectiveAcl(target: any, user?: string): symbol { } /** -* Returns the layout acl that is effective on the document passed through as the target. If no layout acls -* have been set, it returns the regular acls for the document target is contained in. -*/ + * Returns the layout acl that is effective on the document passed through as the target. If no layout acls + * have been set, it returns the regular acls for the document target is contained in. + */ function getEffectiveLayoutAcl(target: any, user?: string): symbol { const targetAcls = target[DocAcl]; @@ -248,27 +248,24 @@ function getEffectiveLayoutAcl(target: any, user?: string): symbol { if ((GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') && entity != 'Public') { if (effectiveAcl && HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { effectiveAcl = value as symbol; - } - else{ + } else { effectiveAcl = value as symbol; } } } - if (effectiveAcl){ + if (effectiveAcl) { return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; - } - else{ + } else { return GetEffectiveAcl(Doc.GetProto(target['embedContainer']), user); } } // authored documents are private until an ACL is set. const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc of Proxy, so check __fieldTuples.author and .author if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; - return AclAdmin; + return AclAdmin; } - /** * Recursively distributes the access right for a user across the children of a document and its annotations. * @param key the key storing the access right (e.g. acl-groupname) @@ -286,7 +283,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) updateCachedAcls(target); } - return; + //return; } visited.push(target); -- cgit v1.2.3-70-g09d2 From 06cfe3cbba127d865e788b00561f8a591af1bd81 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Jul 2023 15:32:08 -0400 Subject: more fixes to simplify sharing --- src/client/documents/Documents.ts | 31 ++++++------ src/client/util/CurrentUserUtils.ts | 6 +-- src/client/util/DocumentManager.ts | 2 +- src/client/util/SharingManager.tsx | 9 ++-- src/client/views/DashboardView.tsx | 15 +++--- src/client/views/DocComponent.tsx | 15 +----- src/client/views/PropertiesView.tsx | 55 ++++++++++------------ .../views/collections/CollectionDockingView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 8 +--- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/client/views/search/SearchBox.tsx | 2 +- src/fields/Doc.ts | 16 ++----- src/fields/DocSymbols.ts | 1 - src/fields/util.ts | 39 +++++++-------- 14 files changed, 88 insertions(+), 117 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f1ba852f9..4ebbfbd1c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -169,8 +169,8 @@ export class DocumentOptions { _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers'); - 'acl-Public'?: string; // public permissions - '_acl-Public'?: string; // public permissions + 'acl-Guest'?: string; // public permissions + '_acl-Guest'?: string; // public permissions type?: DTYPEt = new DTypeInfo('type of document', true); type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection @@ -825,8 +825,7 @@ export namespace Docs { const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); // dataProps['acl-Override'] = SharingPermissions.Unset; - dataProps['acl-Public'] = options['acl-Public'] ? options['acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - + dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); dataProps.isSystem = viewProps.isSystem; dataProps.isDataDoc = true; dataProps.author = Doc.CurrentUserEmail; @@ -848,16 +847,17 @@ export namespace Docs { dataDoc.proto = proto; } - const viewFirstProps: { [id: string]: any } = {}; - // viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - // viewFirstProps['acl-Override'] = SharingPermissions.Unset; - viewFirstProps.author = Doc.CurrentUserEmail; + const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail }; + viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); let viewDoc: Doc; // determines whether viewDoc should be created using placeholder Doc or default if (placeholderDoc) { placeholderDoc._height = options._height !== undefined ? Number(options._height) : undefined; placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined; viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + Array.from(Object.keys(placeholderDoc)) + .filter(key => key.startsWith('acl')) + .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key])); } else { viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); } @@ -997,7 +997,7 @@ export namespace Docs { I.rotation = 0; I.defaultDoubleClick = 'click'; I.author_date = new DateField(); - I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; //I['acl-Override'] = SharingPermissions.Unset; I[Initializing] = false; @@ -1169,10 +1169,13 @@ export namespace Docs { const doc = DockDocument( configs.map(c => c.doc), JSON.stringify(layoutConfig), - options, + Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, id ); - configs.map(c => Doc.SetContainer(c.doc, doc)); + configs.map(c => { + Doc.SetContainer(c.doc, doc); + inheritParentAcls(doc, c.doc, false); + }); return doc; } @@ -1332,8 +1335,8 @@ export namespace DocUtils { source, target, { - 'acl-Public': SharingPermissions.Augment, - '_acl-Public': SharingPermissions.Augment, + 'acl-Guest': SharingPermissions.Augment, + '_acl-Guest': SharingPermissions.Augment, title: ComputedField.MakeFunction('generateLinkTitle(self)') as any, link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, @@ -1854,8 +1857,6 @@ export namespace DocUtils { Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString(), true); } - if (ndoc && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, ndoc); - return ndoc; } export function delegateDragFactory(dragFactory: Doc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 11a8dcaf6..2cee37380 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -765,7 +765,7 @@ export class CurrentUserUtils { linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail; linkDocs.author = Doc.CurrentUserEmail; linkDocs.data = new List([]); - linkDocs["acl-Public"] = SharingPermissions.Augment; + linkDocs["acl-Guest"] = SharingPermissions.Augment; doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } @@ -788,7 +788,7 @@ export class CurrentUserUtils { childContextMenuScripts: new List([addToDashboards!,]), childContextMenuLabels: new List(["Add to Dashboards",]), childContextMenuIcons: new List(["user-plus",]), - "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment, childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true, // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, @@ -823,7 +823,7 @@ export class CurrentUserUtils { async () => { const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; - SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); + SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]); }, { fireImmediately: true }); doc.isSystem ?? (doc.isSystem = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3f0848d00..b921b3116 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -194,7 +194,7 @@ export class DocumentManager { var containerDocContext = srcContext ? [srcContext, doc] : [doc]; while ( containerDocContext.length && - containerDocContext[0]?.embedContainer && + DocCast(containerDocContext[0]?.embedContainer) && DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking && (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0])) ) { diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 37c813279..407396cb4 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -159,7 +159,7 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const acl = `acl-${normalizeEmail(user.email)}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); - docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { + docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); @@ -176,7 +176,7 @@ export class SharingManager extends React.Component<{}> { const acl = `acl-${normalizeEmail(StrCast(group.title))}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); - docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { + docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); if (group instanceof Doc) { @@ -218,7 +218,7 @@ export class SharingManager extends React.Component<{}> { */ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; - if (shareWith !== 'Public' && shareWith !== 'Override') { + if (shareWith !== 'Guest') { const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); @@ -291,7 +291,6 @@ export class SharingManager extends React.Component<{}> { private sharingOptions(uniform: boolean, override?: boolean) { const dropdownValues: string[] = Object.values(SharingPermissions); if (!uniform) dropdownValues.unshift('-multiple-'); - if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1); return dropdownValues.map(permission => (
@@ -389,13 +384,13 @@ export class PropertiesView extends React.Component { /** * @returns a colored dropdown bar reflective of the permission */ - colorACLDropDown(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { + colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) { var shareImage = ReverseHierarchyMap.get(permission)?.image; return (
-
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : concat(shareImage, ' ', permission)}
+
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}
@@ -430,15 +425,16 @@ export class PropertiesView extends React.Component { const individualTableEntries = []; const usersAdded: string[] = []; // all shared users being added - organized by denormalized email + const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc); // adds each user to usersAdded SharingManager.Instance.users.forEach(eachUser => { var userOnDoc = true; - if (this.selectedDoc) { - if (this.selectedDoc['acl-' + normalizeEmail(eachUser.user.email)] == '' || this.selectedDoc['acl-' + normalizeEmail(eachUser.user.email)] == undefined) { + if (seldoc) { + if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) { userOnDoc = false; } } - if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email != 'Public' && eachUser.user.email != target.author) { + if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) { usersAdded.push(eachUser.user.email); } }); @@ -447,15 +443,16 @@ export class PropertiesView extends React.Component { usersAdded.sort(this.sortUsers); usersAdded.map(userEmail => { const userKey = `acl-${normalizeEmail(userEmail)}`; - var permission = StrCast(target[userKey]); + var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true); + var permission = StrCast(aclField); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user }); // adds current user var userEmail = Doc.CurrentUserEmail; + if (userEmail == 'guest') userEmail = 'Guest'; const userKey = `acl-${normalizeEmail(userEmail)}`; - if (userEmail == 'guest') userEmail = 'Public'; - if (!usersAdded.includes(userEmail) && userEmail != 'Public' && userEmail != target.author) { + if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) { var permission; if (this.layoutDocAcls) { if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; @@ -473,7 +470,7 @@ export class PropertiesView extends React.Component { const groupList = GroupManager.Instance?.allGroups || []; groupList.sort(this.sortGroups); groupList.map(group => { - if (group.title != 'Public' && this.selectedDoc) { + if (group.title != 'Guest' && this.selectedDoc) { const groupKey = 'acl-' + normalizeEmail(StrCast(group.title)); if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) { var permission; @@ -489,17 +486,11 @@ export class PropertiesView extends React.Component { }); // public permission - const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Public']); + const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']); return (

- Public / Guest Users -
{this.colorACLDropDown('Public', showAdmin, publicPermission!, false)}
-
- {' '} -

Individual Users with Access to this Document{' '} -
{
{individualTableEntries}
}
{groupTableEntries.length > 0 ? (
@@ -510,6 +501,12 @@ export class PropertiesView extends React.Component {
{
{groupTableEntries}
}
) : null} + Guest +
{this.colorACLDropDown('Guest', true, publicPermission!, true)}
+
+ {' '} +

Individual Users with Access to this Document{' '} +
); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 13d3b2cdc..8d1b46ebb 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -517,7 +517,7 @@ export class CollectionDockingView extends CollectionSubView() { _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - inheritParentAcls(this.rootDoc, docToAdd); + inheritParentAcls(this.rootDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -560,7 +560,7 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - inheritParentAcls(this.dataDoc, Doc.GetProto(docToAdd)); + inheritParentAcls(this.dataDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 764b1e08a..e2718b52d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; +import { distributeAcls, GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; import { intersectRect, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; @@ -390,11 +390,7 @@ export class MarqueeView extends React.Component { - Doc.SetContainer(d, newCollection); - d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - }); + selected.forEach(d => Doc.SetContainer(d, newCollection)); this.hideMarquee(); return newCollection; }); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 5bba51ec8..a0b0caf98 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -597,7 +597,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (contentFrameNumber !== undefined) { CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); } else { - dv.rootDoc['_' + fieldKey] = color; + dv.dataDoc[fieldKey] = color; } }); } else { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index d13c09443..ab9eebd78 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -222,7 +222,7 @@ export class SearchBox extends ViewBoxBaseComponent() { 'width', 'layout_autoHeight', 'acl-Override', - 'acl-Public', + 'acl-Guest', 'embedContainer', 'zIndex', 'height', diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 698e09915..6121668e3 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -20,7 +20,6 @@ import { AclEdit, AclPrivate, AclReadonly, - AclUnset, Animation, CachedUpdates, DirectLinks, @@ -134,7 +133,6 @@ export const HierarchyMapping: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); @@ -380,7 +378,8 @@ export class Doc extends RefField { return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } @computed get __LAYOUT__(): Doc | undefined { - const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); + const self = this[SelfProxy]; + const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')]; @@ -389,7 +388,7 @@ export class Doc extends RefField { } else { return Cast(layoutField, Doc, null); } - return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; + return Cast(self[renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; } return undefined; } @@ -480,11 +479,6 @@ export namespace Doc { export function SetContainer(doc: Doc, container: Doc) { doc.embedContainer = container; - if (Doc.GetProto(container).author === doc.author) { - Object.keys(Doc.GetProto(container)) - .filter(key => key.startsWith('acl') && !key.includes(Doc.CurrentUserEmailNormalized)) - .forEach(key => (doc[key] = Doc.GetProto(container)[key])); - } } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; @@ -697,7 +691,7 @@ export namespace Doc { Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } embedding.createdFrom = doc; - embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length + 1; + embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1; embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`); embedding.author = Doc.CurrentUserEmail; @@ -1027,7 +1021,6 @@ export namespace Doc { Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy); } copy.embedContainer = undefined; - Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared'); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } @@ -1087,7 +1080,6 @@ export namespace Doc { const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); target.layout_fieldKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared'); return applied; } return undefined; diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index eab26ed10..66d1ab094 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -13,7 +13,6 @@ export const DocFields = Symbol('DocFields'); export const DocCss = Symbol('DocCss'); export const DocAcl = Symbol('DocAcl'); export const DirectLinks = Symbol('DocDirectLinks'); -export const AclUnset = Symbol('DocAclUnset'); export const AclPrivate = Symbol('DocAclOwnerOnly'); export const AclReadonly = Symbol('DocAclReadOnly'); export const AclAugment = Symbol('DocAclAugment'); diff --git a/src/fields/util.ts b/src/fields/util.ts index 034229319..e89cb1fb1 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -131,17 +131,19 @@ export function denormalizeEmail(email: string) { /** * Copies parent's acl fields to the child */ -export function inheritParentAcls(parent: Doc, child: Doc) { +export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) { if (GetEffectiveAcl(parent) !== AclAdmin) return; - for (const key of Object.keys(parent)) { - // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); - if (symbol) { - const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - key.startsWith('acl') && distributeAcls(key, sharePermission, child); - } - } + Object.keys(parent) + .filter(key => key.startsWith('acl')) + .forEach(key => { + // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private. + // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; + const parAcl = ReverseHierarchyMap.get(StrCast(parent[key]))?.acl; + if (parAcl) { + const sharePermission = HierarchyMapping.get(parAcl)?.name; + sharePermission && distributeAcls(key, sharePermission, child, undefined, false, layoutOnly); + } + }); } /** @@ -160,7 +162,6 @@ export function inheritParentAcls(parent: Doc, child: Doc) { * Unset: Remove a sharing permission (eg., used ) */ export enum SharingPermissions { - Unset = 'None', Admin = 'Admin', Edit = 'Edit', Augment = 'Augment', @@ -233,7 +234,7 @@ function getEffectiveAcl(target: any, user?: string): symbol { * @param allowUpgrade whether permissions can be made less restrictive * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ -export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) { +export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) { const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`; if (!visited) visited = [] as Doc[]; if (!target || visited.includes(target) || key === selfKey) return; @@ -243,19 +244,19 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc const dataDoc = target[DocData]; const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0; const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0; - if (dataDoc && (allowUpgrade !== false|| !dataDoc[key] || curVal > aclVal)) { + if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) { // propagate ACLs to links, children, and annotations - LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade? true: false)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false)); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade ? true: false); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true: false); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade? true: false); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade? true: false); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); if (GetEffectiveAcl(dataDoc) === AclAdmin) { @@ -266,7 +267,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { + if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !Doc.GetT(target, key, 'boolean', true) || ReverseHierarchyMap.get(StrCast(target[key]))!.level > aclVal)) { target[key] = acl; layoutDocChanged = true; } -- cgit v1.2.3-70-g09d2 From 54308259a8cd3ac98aaee550ea01ad97f17172e6 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Mon, 17 Jul 2023 23:11:35 -0400 Subject: so many updates --- deploy/index.html | 7 +- deploy/loader.css | 5 +- package-lock.json | 232 +++---- package.json | 2 +- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 63 +- src/client/util/DropConverter.ts | 2 +- src/client/util/ReportManager.tsx | 4 +- src/client/util/SettingsManager.scss | 93 +-- src/client/util/SettingsManager.tsx | 186 ++++-- src/client/views/AntimodeMenu.scss | 32 +- src/client/views/AntimodeMenu.tsx | 5 + src/client/views/DashboardView.scss | 22 + src/client/views/DashboardView.tsx | 98 ++- src/client/views/EditableView.tsx | 75 ++- src/client/views/GestureOverlay.tsx | 2 +- src/client/views/MainView.scss | 20 - src/client/views/MainView.tsx | 13 +- src/client/views/MainViewModal.scss | 6 +- src/client/views/MainViewModal.tsx | 7 +- src/client/views/PropertiesButtons.scss | 8 +- src/client/views/PropertiesButtons.tsx | 98 ++- .../views/PropertiesDocBacklinksSelector.scss | 2 +- .../views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesDocContextSelector.tsx | 1 + src/client/views/PropertiesSection.scss | 24 + src/client/views/PropertiesSection.tsx | 51 ++ src/client/views/PropertiesView.scss | 364 +--------- src/client/views/PropertiesView.tsx | 743 ++++++++++----------- src/client/views/StyleProvider.tsx | 10 +- src/client/views/UndoStack.scss | 9 +- src/client/views/UndoStack.tsx | 48 +- src/client/views/collections/CollectionMenu.scss | 651 +----------------- src/client/views/collections/CollectionMenu.tsx | 51 +- .../views/collections/CollectionTreeView.tsx | 2 + src/client/views/collections/TabDocView.scss | 17 - src/client/views/collections/TabDocView.tsx | 103 ++- src/client/views/collections/TreeView.scss | 36 +- src/client/views/collections/TreeView.tsx | 35 +- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 69 +- .../collectionLinear/CollectionLinearView.tsx | 51 +- src/client/views/global/globalScripts.ts | 49 +- src/client/views/linking/LinkPopup.scss | 5 - src/client/views/linking/LinkPopup.tsx | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- .../views/nodes/FontIconBox/ButtonInterface.ts | 12 + .../views/nodes/FontIconBox/FontIconBadge.scss | 11 + .../views/nodes/FontIconBox/FontIconBadge.tsx | 37 + .../views/nodes/FontIconBox/FontIconBox.scss | 438 ++++++++++++ src/client/views/nodes/FontIconBox/FontIconBox.tsx | 412 ++++++++++++ src/client/views/nodes/button/FontIconBox.scss | 438 ------------ src/client/views/nodes/button/FontIconBox.tsx | 385 ----------- src/client/views/pdf/AnchorMenu.tsx | 121 ++-- src/client/views/search/SearchBox.scss | 3 +- src/client/views/search/SearchBox.tsx | 7 +- src/client/views/selectedDoc/SelectedDocView.scss | 3 + src/client/views/selectedDoc/SelectedDocView.tsx | 47 ++ src/client/views/selectedDoc/index.ts | 1 + src/client/views/topbar/TopBar.tsx | 36 +- 60 files changed, 2279 insertions(+), 2991 deletions(-) create mode 100644 src/client/views/PropertiesSection.scss create mode 100644 src/client/views/PropertiesSection.tsx create mode 100644 src/client/views/nodes/FontIconBox/ButtonInterface.ts create mode 100644 src/client/views/nodes/FontIconBox/FontIconBadge.scss create mode 100644 src/client/views/nodes/FontIconBox/FontIconBadge.tsx create mode 100644 src/client/views/nodes/FontIconBox/FontIconBox.scss create mode 100644 src/client/views/nodes/FontIconBox/FontIconBox.tsx delete mode 100644 src/client/views/nodes/button/FontIconBox.scss delete mode 100644 src/client/views/nodes/button/FontIconBox.tsx create mode 100644 src/client/views/selectedDoc/SelectedDocView.scss create mode 100644 src/client/views/selectedDoc/SelectedDocView.tsx create mode 100644 src/client/views/selectedDoc/index.ts (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/deploy/index.html b/deploy/index.html index f8003f126..c8ad2a8d4 100644 --- a/deploy/index.html +++ b/deploy/index.html @@ -5,7 +5,7 @@ Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); @@ -609,7 +609,7 @@ export class CurrentUserUtils { const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts:DocumentOptions = { title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move', - childDontRegisterViews: true, linearView_IsExpanded: true, linearView_Expandable: true, ignoreClick: true + childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); @@ -625,12 +625,12 @@ export class CurrentUserUtils { } static viewTools(): Button[] { return [ - { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { @@ -645,9 +645,13 @@ export class CurrentUserUtils { { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, - { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Alignment",toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, + subMenu: [ + { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, + { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + ] + }, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, @@ -695,17 +699,17 @@ export class CurrentUserUtils { title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected - { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'return { toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, - { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available - { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected - { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected + { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected + { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; } @@ -713,7 +717,6 @@ export class CurrentUserUtils { static setupContextMenuButton(params:Button, btnDoc?:Doc) { const reqdOpts:DocumentOptions = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, - backgroundColor: params.backgroundColor ??"transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background color: Colors.WHITE, isSystem: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth, @@ -722,21 +725,20 @@ export class CurrentUserUtils { }; const reqdFuncs:{[key:string]:any} = { ...params.funcs, - backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List(['width', "linearView_IsExpanded"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; + const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); - if (!params.subMenu) { + if (!params.subMenu) { // button does not have a sub menu return this.setupContextMenuButton(params, menuBtnDoc); - } else { - const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List(['width', "linearView_IsExpanded"]), + } else { // linear view + const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List(['width', "linearView_IsOpen"]), childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true, linearView_SubMenu: true, linearView_Expandable: true, }; const items = params.subMenu?.map(sub => @@ -847,6 +849,11 @@ export class CurrentUserUtils { doc.fontHighlight ?? (doc.fontHighlight = ""); doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false); doc.savedFilters ?? (doc.savedFilters = new List()); + doc.userBackgroundColor ?? (doc.userBackgroundColor = Colors.DARK_GRAY); + addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${doc.userBackgroundColor} !important` }); + doc.userVariantColor ?? (doc.userVariantColor = Colors.MEDIUM_BLUE); + doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY); + doc.userTheme ?? (doc.userTheme = ColorScheme.Dark); doc.filterDocCount = 0; doc.treeViewFreezeChildren = "remove|add"; this.setupLinkDocs(doc, linkDatabaseId); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 47997cc5c..f235be192 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -9,7 +9,7 @@ import { RichTextField } from '../../fields/RichTextField'; import { ImageField } from '../../fields/URLField'; import { ScriptingGlobals } from './ScriptingGlobals'; import { listSpec } from '../../fields/Schema'; -import { ButtonType } from '../views/nodes/button/FontIconBox'; +import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox'; export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined, templateField: string = '') { if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated diff --git a/src/client/util/ReportManager.tsx b/src/client/util/ReportManager.tsx index 4c1020455..89c17e42f 100644 --- a/src/client/util/ReportManager.tsx +++ b/src/client/util/ReportManager.tsx @@ -11,7 +11,7 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/button/FontIconBox'; +import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { DragManager } from './DragManager'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; @@ -290,7 +290,7 @@ export class ReportManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }} + dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.UserDoc().userColor, 'string', null) }} /> ); } diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index c41adfdd7..bca649bc3 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -2,41 +2,20 @@ .settings-interface { //background-color: whitesmoke !important; - color: grey; width: 450px; - - button { - background: #315a96; - outline: none; - border-radius: 5px; - border: 0px; - color: #fcfbf7; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - padding: 10px; - margin: 10px; - transition: transform 0.2s; - margin: 2px; - } } .settings-title { font-size: 25px; font-weight: bold; padding-right: 10px; - color: black; } .settings-username { font-size: 12px; padding-right: 15px; - color: black; margin-top: 10px; margin-bottom: 10px; - /* right: 135; */ - // position: absolute; - // left: 243; } .settings-section { @@ -49,7 +28,6 @@ font-size: 16; font-weight: bold; text-align: left; - color: black; width: 80; margin-right: 50px; } @@ -61,45 +39,10 @@ .password-content { display: flex; + width: 100%; flex-direction: column; align-items: flex-start; - - .password-content-inputs { - display: flex; - flex-direction: column; - width: 100%; - font-size: 10px; - align-items: flex-start; - - .password-inputs { - border: 1px solid rgb(160, 160, 160); - margin-bottom: 15px; - width: 130; - color: $medium-gray; - border-radius: 5px; - padding: 7px; - } - } - - .password-content-buttons { - display: flex; - gap: 5px; - justify-content: center; - align-items: flex-start; - flex-direction: column; - - .password-submit { - //margin-left: 85px; - margin-top: 5px; - } - - .password-forgot { - //margin-left: 65px; - //margin-top: -20px; - font-size: 12px; - white-space: nowrap; - } - } + gap: 5px; } .accounts-content { @@ -115,7 +58,6 @@ width: 80%; height: 35px; margin-right: 10px; - color: black; border-radius: 5px; &:hover { @@ -139,12 +81,6 @@ } } - .playground-text { - color: black; - margin-right: 10px; - margin-top: 2; - } - .acl-text { color: black; margin-top: 2; @@ -176,7 +112,6 @@ .appearances-content { display: flex; - color: black; font-size: 10px; .preferences-color { @@ -201,7 +136,6 @@ margin-top: 2px; .preferences-font-text { - color: black; margin-top: 4; margin-right: 4; margin-bottom: 2px; @@ -216,7 +150,6 @@ .font-select { height: 35px; - color: black; font-size: 9; margin-right: 6; border-radius: 5px; @@ -229,7 +162,6 @@ .size-select { height: 35px; - color: black; font-size: 9; border-radius: 5px; width: 30%; @@ -241,7 +173,6 @@ } .preferences-check { - color: black; margin-right: 4; margin-bottom: -3; margin-left: 5; @@ -265,7 +196,6 @@ .settings-content { - background: #e4e4e4; padding: 10px; width: 500px; flex: 1 1 auto; @@ -296,7 +226,6 @@ } h1 { - color: #121721; text-transform: uppercase; letter-spacing: 2px; font-size: 19; @@ -325,7 +254,6 @@ .padding { padding: 0 0 0 20px; - color: black; } } } @@ -336,16 +264,11 @@ min-height: 250px; height: 100%; width: 100%; - - .settings-content { - background-color: $off-white; - } } .settings-panel { position: relative; min-width: 150px; - background-color: $light-blue; .settings-user { position: absolute; @@ -370,16 +293,11 @@ .settings-tabs { // font-size: 16px; font-weight: 600; - color: black; .tab-control { padding: 10px; border-bottom: 1px solid #9f9e9e; cursor: pointer; - - &.active { - background-color: #fdfdfd; - } } } @@ -398,16 +316,18 @@ .tab-content { display: flex; + flex-flow: row wrap; + gap: 10px; + overflow: hidden; .tab-column { - flex: 0 0 50%; + flex: 0 0 calc(50% - 10px); flex-direction: column; display: flex; justify-content: flex-start; align-items: flex-start; .tab-column-title { - color: $dark-gray; font-size: 16px; font-weight: bold; margin-bottom: 10px; @@ -420,6 +340,7 @@ align-items: flex-start; flex-direction: column; gap: 10px; + width: 100%; } } } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 09d6d034f..b6df5f26a 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -11,12 +11,12 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/button/FontIconBox'; +import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { DragManager } from './DragManager'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; import { undoBatch } from './UndoManager'; -import { Button, ColorPicker, Dropdown, DropdownType, Group, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { BsGoogle } from 'react-icons/bs' import { FaFillDrip, FaPalette } from 'react-icons/fa' const higflyout = require('@hig/flyout'); @@ -24,9 +24,10 @@ export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; export enum ColorScheme { - Dark = '-Dark', - Light = '-Light', - System = '-MatchSystem', + Dark = 'Dark', + Light = 'Light', + System = 'Match System', + Custom = 'Custom' } export enum freeformScrollMode { @@ -53,8 +54,8 @@ export class SettingsManager extends React.Component<{}> { @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } - @computed get colorScheme() { - return Doc.ActiveDashboard?.colorScheme; + @computed get userTheme() { + return Doc.UserDoc().userTheme; } constructor(props: {}) { @@ -76,14 +77,31 @@ export class SettingsManager extends React.Component<{}> { } }; + @computed get userColor() { + return StrCast(Doc.UserDoc().userColor) + } + + @computed get userVariantColor() { + return StrCast(Doc.UserDoc().userVariantColor) + } + + @computed get userBackgroundColor() { + return StrCast(Doc.UserDoc().userBackgroundColor) + } + @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice')); @undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_showTitle = (e.currentTarget as any).value ? 'title' : undefined)); @undoBatch changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font)); @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val)); - @undoBatch switchActiveBackgroundColor = action((color: string) => (Doc.UserDoc().activeCollectionBackground = color)); + @undoBatch switchUserBackgroundColor = action((color: string) => { + Doc.UserDoc().userBackgroundColor = color; + addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` }); + }); @undoBatch switchUserColor = action((color: string) => { - Doc.SharingDoc().userColor = undefined; - Doc.GetProto(Doc.SharingDoc()).userColor = color; + Doc.UserDoc().userColor = color; + }); + @undoBatch switchUserVariantColor = action((color: string) => { + Doc.UserDoc().userVariantColor = color; }); @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; @@ -96,22 +114,24 @@ export class SettingsManager extends React.Component<{}> { @undoBatch @action changeColorScheme = action((scheme: string) => { - const activeDashboard = Doc.ActiveDashboard; - if (!activeDashboard) return; + Doc.UserDoc().userTheme = scheme; switch (scheme) { case ColorScheme.Light: - activeDashboard.colorScheme = ColorScheme.Light - addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: '#d3d3d3 !important' }); + this.switchUserColor("#323232") + this.switchUserBackgroundColor("#DFDFDF") + this.switchUserVariantColor("#BDDDF5") break; case ColorScheme.Dark: - activeDashboard.colorScheme = ColorScheme.Dark; - addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' }); + this.switchUserColor("#DFDFDF") + this.switchUserBackgroundColor("#323232") + this.switchUserVariantColor("#4476F7") + break; + case ColorScheme.Custom: break; case ColorScheme.System: default: - activeDashboard.colorScheme = ColorScheme.System; window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + e.matches ? ColorScheme.Dark : ColorScheme.Light; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) }); break; } @@ -120,18 +140,16 @@ export class SettingsManager extends React.Component<{}> { @computed get colorsContent() { - const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System]; - const schemeMap = ['Light', 'Dark', 'Match System']; - + const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.Custom, ColorScheme.System]; + const schemeMap = ['Light', 'Dark', 'Custom', 'Match System']; + const userTheme = StrCast(Doc.UserDoc().userTheme); return ( -
- } selectedColor={StrCast(Doc.UserDoc().activeCollectionBackground)} setSelectedColor={this.switchActiveBackgroundColor}/> - } selectedColor={StrCast(Doc.SharingDoc().userColor)} setSelectedColor={this.switchUserColor}/> +
this.changeColorScheme(scheme as string)} items={colorSchemes.map((scheme, i) => ( { @@ -140,8 +158,14 @@ export class SettingsManager extends React.Component<{}> { } ))} dropdownType={DropdownType.SELECT} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} + fillWidth /> + {userTheme === ColorScheme.Custom && + } selectedColor={this.userColor} setSelectedColor={this.switchUserColor}/> + } selectedColor={this.userBackgroundColor} setSelectedColor={this.switchUserBackgroundColor}/> + } selectedColor={this.userVariantColor} setSelectedColor={this.switchUserVariantColor}/> + }
); } @@ -155,7 +179,7 @@ export class SettingsManager extends React.Component<{}> { toggleType={ToggleType.SWITCH} onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} toggleStatus={Doc.UserDoc().layout_showTitle !== undefined} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} toggleStatus={FontIconBox.GetShowLabels()} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} toggleStatus={FontIconBox.GetRecognizeGestures()} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} />
@@ -238,6 +262,15 @@ export class SettingsManager extends React.Component<{}> {
{/* */} + {}} + /> { return { @@ -252,7 +285,8 @@ export class SettingsManager extends React.Component<{}> { type={Type.TERT} selectedVal={StrCast(Doc.UserDoc().fontFamily)} setSelectedVal={(val) => {this.changeFontFamily(val as string)}} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} + fillWidth />
@@ -262,8 +296,7 @@ export class SettingsManager extends React.Component<{}> { } @action - changeVal = (e: React.ChangeEvent, pass: string) => { - const value = (e.target as any).value; + changeVal = (value: string, pass: string) => { switch (pass) { case 'curr': this.curr_password = value; @@ -280,16 +313,33 @@ export class SettingsManager extends React.Component<{}> { @computed get passwordContent() { return (
-
- this.changeVal(e, 'curr')} /> - this.changeVal(e, 'new')} /> - this.changeVal(e, 'conf')} /> -
-
- {!this.passwordResultText ? null :
{this.passwordResultText}
} -
+ this.changeVal(val as string, 'curr')} + fillWidth + password + /> + this.changeVal(val as string, 'new')} + fillWidth + password + /> + this.changeVal(val as string, 'conf')} + fillWidth + password + /> + {!this.passwordResultText ? null :
{this.passwordResultText}
} +
); } @@ -346,14 +396,15 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement='bottom-start' - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} + fillWidth />
@@ -379,7 +430,7 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement='bottom-start' - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} />
@@ -390,12 +441,12 @@ export class SettingsManager extends React.Component<{}> { text={"Manage Groups"} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}/>
@@ -414,23 +465,35 @@ export class SettingsManager extends React.Component<{}> { { title: 'Appearance', ele: this.appearanceContent }, { title: 'Text', ele: this.textContent }, ]; - return (
-
+
- {tabs.map(tab => ( -
(this.activeTab = tab.title))}> - {tab.title} -
- ))} + {tabs.map(tab => { + const isActive = this.activeTab === tab.title + return ( +
(this.activeTab = tab.title)) + }> + {tab.title} +
+ ) + })}
-
{Doc.CurrentUserEmail}
+
{Doc.CurrentUserEmail}
@@ -439,12 +502,13 @@ export class SettingsManager extends React.Component<{}> {
-
+
{tabs.map(tab => (
{tab.ele} @@ -462,7 +526,7 @@ export class SettingsManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }} + dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }} /> ); } diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss index 8a0e5480e..b205a0f1e 100644 --- a/src/client/views/AntimodeMenu.scss +++ b/src/client/views/AntimodeMenu.scss @@ -5,11 +5,15 @@ position: absolute; z-index: 10001; height: $antimodemenu-height; - background: $dark-gray; - border-bottom: $standard-border; + width: fit-content; + border-radius: $standard-border-radius; + overflow: hidden; // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); // border-radius: 0px 6px 6px 6px; display: flex; + justify-content: center; + align-items: center; + gap: 3px; &.with-rows { flex-direction: column @@ -20,30 +24,6 @@ height: 35px; } - .antimodeMenu-button { - background-color: transparent; - width: 35px; - height: 35px; - padding: 5; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - position: relative; - - .svg { - margin: 0; - } - - &.active { - background-color: #121212; - } - } - - .antimodeMenu-button:hover { - background-color: rgba(0, 0, 0, 0.4); - } - .antimodeMenu-dragger { height: 100%; transition: width .2s; diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index de1207ce4..c41ea7053 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -1,6 +1,8 @@ import React = require('react'); import { observable, action, runInAction } from 'mobx'; import './AntimodeMenu.scss'; +import { StrCast } from '../../fields/Types'; +import { Doc } from '../../fields/Doc'; export interface AntimodeMenuProps {} /** @@ -148,6 +150,7 @@ export abstract class AntimodeMenu extends React.Co left: this._left, top: this._top, opacity: this._opacity, + background: StrCast(Doc.UserDoc().userBackgroundColor), transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, @@ -173,6 +176,7 @@ export abstract class AntimodeMenu extends React.Co height: 'inherit', width: 200, opacity: this._opacity, + background: StrCast(Doc.UserDoc().userBackgroundColor), transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, @@ -195,6 +199,7 @@ export abstract class AntimodeMenu extends React.Co left: this._left, top: this._top, opacity: this._opacity, + background: StrCast(Doc.UserDoc().userBackgroundColor), transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index b8a6f6c05..b95312ea0 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -14,6 +14,7 @@ flex-direction: column; width: 250px; min-width: 250px; + gap: 5px; } .all-dashboards { @@ -56,15 +57,26 @@ display: flex; justify-content: center; align-items: center; + position: relative; &:hover { color: $light-blue; border: solid 2px $light-blue; } + + .background { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + z-index: -1; + } } .dashboard-container { border-radius: 10px; + position: relative; cursor: pointer; width: 250px; height: 200px; @@ -99,10 +111,20 @@ .more { z-index: 100; } + + .background { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + z-index: -1; + } } .new-dashboard { color: $dark-gray; + padding: 10px; display: flex; width: 100%; height: 100%; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 3bd6ac61c..dd1079d88 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, ColorPicker, FontSize, IconButton, Size, Type } from 'browndash-components'; +import { Button, ColorPicker, EditableText, FontSize, IconButton, Size, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -24,7 +24,7 @@ import { ContextMenu } from './ContextMenu'; import './DashboardView.scss'; import { Colors } from './global/globalEnums'; import { MainViewModal } from './MainViewModal'; -import { ButtonType } from './nodes/button/FontIconBox'; +import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { FaPlus } from 'react-icons/fa'; enum DashboardGroup { @@ -43,13 +43,16 @@ export class DashboardView extends React.Component { @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName: string | undefined = undefined; - @observable private newDashboardColor: string | undefined = undefined; + @observable private newDashboardColor: string | undefined = "#AFAFAF"; @action abortCreateNewDashboard = () => { this.newDashboardName = undefined; }; @action setNewDashboardName(name: string) { this.newDashboardName = name; } + @action setNewDashboardColor(color: string) { + this.newDashboardColor = color; + } @action selectDashboardGroup = (group: DashboardGroup) => { @@ -95,23 +98,33 @@ export class DashboardView extends React.Component { const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const placeholder = `Dashboard ${dashboardCount}`; return ( -
+
Create New Dashboard
-
- Title - this.setNewDashboardName((e.target as any).value)} /> -
-
- Background - { - this.newDashboardColor = color; - }} - /> -
+ this.setNewDashboardName(val as string)} + fillWidth + /> + { + this.setNewDashboardColor(color); + }} + />
-
); @@ -150,32 +163,49 @@ export class DashboardView extends React.Component { }; render() { + const color = StrCast(Doc.UserDoc().userColor) + const variant = StrCast(Doc.UserDoc().userVariantColor) return ( <>
-
-
-
this.selectDashboardGroup(DashboardGroup.MyDashboards)}> - My Dashboards -
-
this.selectDashboardGroup(DashboardGroup.SharedDashboards)}> - Shared Dashboards -
+
{this.getDashboards().map(dashboard => { const href = ImageCast(dashboard.thumb)?.url.href; return ( -
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> +
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}>
- e.stopPropagation()} defaultValue={StrCast(dashboard.title)} onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} /> + (Doc.GetProto(dashboard).title = val)} + /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
-
+
); })} @@ -200,6 +234,10 @@ export class DashboardView extends React.Component { this.setNewDashboardName(''); }}> + +
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 7043edcee..4deed5ed9 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -6,6 +6,7 @@ import { ObjectField } from '../../fields/ObjectField'; import './EditableView.scss'; import { DocumentIconContainer } from './nodes/DocumentIcon'; import { OverlayView } from './OverlayView'; +import { EditableText } from 'browndash-components'; export interface EditableProps { /** @@ -231,39 +232,49 @@ export class EditableView extends React.Component { onChange: this.props.autosuggestProps.onChange, }} /> - ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? ( - (this._inputref = r)} - style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }} - placeholder={this.props.placeholder} - onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} - defaultValue={this.props.GetValue()} - autoFocus={true} - onChange={this.onChange} - onKeyDown={this.onKeyDown} - onKeyPress={this.stopPropagation} - onPointerDown={this.stopPropagation} - onClick={this.stopPropagation} - onPointerUp={this.stopPropagation} + ) : - ) : ( -