aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/DictationOverlay.tsx1
-rw-r--r--src/client/views/DocComponent.tsx48
-rw-r--r--src/client/views/DocumentDecorations.tsx11
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/MainViewModal.scss5
-rw-r--r--src/client/views/MainViewModal.tsx7
-rw-r--r--src/client/views/MetadataEntryMenu.tsx4
-rw-r--r--src/client/views/collections/CollectionMenu.tsx6
-rw-r--r--src/client/views/collections/CollectionSubView.tsx6
-rw-r--r--src/client/views/collections/CollectionView.tsx94
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx41
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx6
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx7
-rw-r--r--src/client/views/nodes/DocumentView.tsx29
-rw-r--r--src/client/views/nodes/FontIconBox.scss3
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx8
-rw-r--r--src/client/views/pdf/PDFMenu.tsx2
19 files changed, 171 insertions, 121 deletions
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 65770c0bb..9ed14509f 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -66,6 +66,7 @@ export class DictationOverlay extends React.Component {
interactive={false}
dialogueBoxStyle={dialogueBoxStyle}
overlayStyle={overlayStyle}
+ closeOnExternalClick={this.initiateDictationFade}
/>);
}
} \ No newline at end of file
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9b9a28f0f..95c1bcda8 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast, AclSym, AclReadonly, AclAddonly } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -7,6 +7,8 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { List } from '../../fields/List';
import { DateField } from '../../fields/DateField';
import { ScriptField } from '../../fields/ScriptField';
+import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util';
+import { SharingPermissions } from '../util/SharingManager';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -91,6 +93,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
// key where data is stored
@computed get fieldKey() { return this.props.fieldKey; }
+ private AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit]
+ ]);
+
lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
styleFromLayoutString = (scale: number) => {
@@ -117,11 +126,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
docs.map(doc => doc.annotationOn = undefined);
const targetDataDoc = this.dataDoc;
const value = DocListCast(targetDataDoc[this.annotationKey]);
- const result = value.filter(v => !docs.includes(v));
- if (result.length !== value.length) {
- targetDataDoc[this.annotationKey] = new List<Doc>(result);
+ const toRemove = value.filter(v => docs.includes(v));
+ // can't assign new List<Doc>(result) to this because you can't assign new values in addonly
+ if (toRemove.length !== 0) {
+ toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.annotationKey, doc));
return true;
}
+
return false;
}
// if the moved document is already in this overlay collection nothing needs to be done.
@@ -137,15 +148,30 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.annotationKey]);
const added = docs.filter(d => !docList.includes(d));
+ const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+
if (added.length) {
- if (this.dataDoc[AclSym] === AclReadonly) {
+ if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
return false;
- } else if (this.dataDoc[AclSym] === AclAddonly) {
- added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
- } else {
- added.map(doc => doc.context = this.props.Document);
- targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ else {
+ if (this.props.Document[AclSym]) {
+ added.forEach(d => {
+ const dataDoc = d[DataSym];
+ dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+ for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+ dataDoc[key] = d[key] = this.AclMap.get(value);
+ }
+ });
+ }
+ if (effectiveAcl === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
+ }
+ else {
+ added.map(doc => doc.context = this.props.Document);
+ targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
}
return true;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 35c040f86..fec4ad9e0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -293,13 +293,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const doc = Document(element.rootDoc);
if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
doc.rotation = Number(doc.rotation) + Number(angle);
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
-
+ const inks = Cast(doc.data, InkField)?.inkData;
+ if (inks) {
const newPoints: { X: number, Y: number }[] = [];
- for (var i = 0; i < ink.length; i++) {
- const newX = Math.cos(angle) * (ink[i].X - this._centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - this._centerPoints[index].Y) + this._centerPoints[index].X;
- const newY = Math.sin(angle) * (ink[i].X - this._centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - this._centerPoints[index].Y) + this._centerPoints[index].Y;
+ for (const ink of inks) {
+ const newX = Math.cos(angle) * (ink.X - this._centerPoints[index].X) - Math.sin(angle) * (ink.Y - this._centerPoints[index].Y) + this._centerPoints[index].X;
+ const newY = Math.sin(angle) * (ink.X - this._centerPoints[index].X) + Math.cos(angle) * (ink.Y - this._centerPoints[index].Y) + this._centerPoints[index].Y;
newPoints.push({ X: newX, Y: newY });
}
doc.data = new InkField(newPoints);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 8c233489e..4dfa7aec8 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -22,6 +22,7 @@ import { DocumentView } from "./nodes/DocumentView";
import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
import PDFMenu from "./pdf/PDFMenu";
import { ContextMenu } from "./ContextMenu";
+import GroupManager from "../util/GroupManager";
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
const modifiers = ["control", "meta", "shift", "alt"];
@@ -108,6 +109,7 @@ export default class KeyManager {
GoogleAuthenticationManager.Instance.cancel();
HypothesisAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
+ GroupManager.Instance.close();
CollectionFreeFormViewChrome.Instance.clearKeep();
break;
case "delete":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 95301b900..7c991c42a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -94,7 +94,7 @@ export class MainView extends React.Component {
public isPointerDown = false;
componentDidMount() {
- DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
+ DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
const tag = document.createElement('script');
diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss
index f5a9ee76c..812fe540b 100644
--- a/src/client/views/MainViewModal.scss
+++ b/src/client/views/MainViewModal.scss
@@ -6,9 +6,10 @@
align-self: center;
align-content: center;
padding: 20px;
- background: gainsboro;
+ // background: gainsboro;
+ background: white;
border-radius: 10px;
- border: 3px solid black;
+ border: 0.5px solid black;
box-shadow: #00000044 5px 5px 10px;
transform: translate(-50%, -50%);
top: 50%;
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index a7bd5882d..249715511 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import "./MainViewModal.scss";
+import { observer } from 'mobx-react';
export interface MainViewOverlayProps {
isDisplayed: boolean;
@@ -9,8 +10,10 @@ export interface MainViewOverlayProps {
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
overlayDisplayedOpacity?: number;
+ closeOnExternalClick?: () => void;
}
+@observer
export default class MainViewModal extends React.Component<MainViewOverlayProps> {
render() {
@@ -18,11 +21,10 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
return !p.isDisplayed ? (null) : (
- <div style={{ pointerEvents: p.isDisplayed ? p.interactive ? "all" : "none" : "none" }}>
+ <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}>
<div
className={"dialogue-box"}
style={{
- backgroundColor: "gainsboro",
borderColor: "black",
...(p.dialogueBoxStyle || {}),
opacity: p.isDisplayed ? dialogueOpacity : 0
@@ -30,6 +32,7 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
>{p.contents}</div>
<div
className={"overlay"}
+ onClick={this.props?.closeOnExternalClick}
style={{
backgroundColor: "gainsboro",
...(p.overlayStyle || {}),
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index ca8a6e1d7..82ec5a5b3 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -38,7 +38,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
let field: Field | undefined | null = null;
let onProto: boolean = false;
let value: string | undefined = undefined;
- let docs = this.props.docs;
+ const docs = this.props.docs;
for (const doc of docs) {
const v = await doc[this._currentKey];
onProto = onProto || !Object.keys(doc).includes(this._currentKey);
@@ -110,7 +110,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
getKeySuggestions = (value: string) => {
value = value.toLowerCase();
- let docs = this.props.docs;
+ const docs = this.props.docs;
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 2be57b6d2..e7c5ca86b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -402,7 +402,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
}
@computed get widthPicker() {
- var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
+ const widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
return !this._widthBtn ? widthPicker :
<div className="btn2-group" key="width">
{widthPicker}
@@ -416,7 +416,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
}
@computed get colorPicker() {
- var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
+ const colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
<div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
return !this._colorBtn ? colorPicker :
<div className="btn-group" key="color">
@@ -431,7 +431,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>;
}
@computed get fillPicker() {
- var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
+ const fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
<div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
return !this._fillBtn ? fillPicker :
<div className="btn-group" key="fill" >
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3794088d4..8a3c2144e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -91,6 +91,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
+ // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly
+ // setTimeout changes it outside of the @computed section
+ setTimeout(() => {
+ if (!this.dataDoc[this.props.annotationsKey || this.props.fieldKey]) this.dataDoc[this.props.annotationsKey || this.props.fieldKey] = new List<Doc>();
+ }, 1000);
return this.dataDoc[this.props.annotationsKey || this.props.fieldKey];
}
@@ -418,4 +423,5 @@ import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTex
import { CollectionView } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
+import { setTimeout } from "timers";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 21b0045d5..8b09281d5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls } from '../../../fields/util';
import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -48,6 +48,7 @@ import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
import CollectionMenu from './CollectionMenu';
+import { SharingPermissions } from '../../util/SharingManager';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -107,6 +108,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ private AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit]
+ ]);
+
get collectionViewType(): CollectionViewType | undefined {
const viewField = StrCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
@@ -129,36 +137,54 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
if (this.props.filterAddDocument?.(doc) === false) {
return false;
}
+
const docs = doc instanceof Doc ? [doc] : doc;
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
const added = docs.filter(d => !docList.includes(d));
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+
if (added.length) {
- if (this.dataDoc[AclSym] === AclReadonly) {
+ if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
return false;
- } else if (this.dataDoc[AclSym] === AclAddonly) {
- added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
- } else {
- added.map(doc => {
- const context = Cast(doc.context, Doc, null);
- if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
- const pushpin = Docs.Create.FontIconDocument({
- title: "pushpin",
- icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
- _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
- });
- pushpin.isPushpin = true;
- Doc.GetProto(pushpin).annotationOn = doc.annotationOn;
- Doc.SetInPlace(doc, "annotationOn", undefined, true);
- Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
- const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
- doc.displayTimecode = undefined;
- }
- doc.context = this.props.Document;
- });
- added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
- targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ else {
+ if (this.props.Document[AclSym]) {
+ // change so it only adds if more restrictive
+ added.forEach(d => {
+ // const dataDoc = d[DataSym];
+ for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+ distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
+ }
+ // dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+ });
+ }
+
+ if (effectiveAcl === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
+ }
+ else {
+ added.map(doc => {
+ const context = Cast(doc.context, Doc, null);
+ if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+ const pushpin = Docs.Create.FontIconDocument({
+ title: "pushpin",
+ icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
+ _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
+ });
+ pushpin.isPushpin = true;
+ Doc.GetProto(pushpin).annotationOn = doc.annotationOn;
+ Doc.SetInPlace(doc, "annotationOn", undefined, true);
+ Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
+ doc.displayTimecode = undefined;
+ }
+ doc.context = this.props.Document;
+ });
+ added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
+ targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
}
return true;
@@ -166,13 +192,15 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
- const docs = doc instanceof Doc ? [doc] : doc as Doc[];
- const targetDataDoc = this.props.Document[DataSym];
- const value = DocListCast(targetDataDoc[this.props.fieldKey]);
- const result = value.filter(v => !docs.includes(v));
- if (result.length !== value.length) {
- targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
- return true;
+ if (GetEffectiveAcl(this.props.Document) === AclEdit || getPlaygroundMode()) {
+ const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+ const targetDataDoc = this.props.Document[DataSym];
+ const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+ const result = value.filter(v => !docs.includes(v));
+ if (result.length !== value.length) {
+ targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+ return true;
+ }
}
return false;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6d44ac967..bfe569853 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -26,7 +26,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this._anchorDisposer?.();
}
@action
- timeout = () => (Date.now() < this._start++ + 1000) && setTimeout(this.timeout, 25);
+ timeout = () => (Date.now() < this._start++ + 1000) && setTimeout(this.timeout, 25)
componentDidMount() {
this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
@@ -111,10 +111,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
return undefined;
}
this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
- const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
- const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
- const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
- const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
+ const acont = this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const a = (acont.length ? acont[0] : this.props.A.ContentDiv).getBoundingClientRect();
+ const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv).getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
b.left, b.top, b.width, b.height,
a.left + a.width / 2, a.top + a.height / 2);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 01b0c81d8..412f91417 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -85,8 +85,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private _lastY: number = 0;
private _downX: number = 0;
private _downY: number = 0;
- private _lastClientY: number | undefined = 0;
- private _lastClientX: number | undefined = 0;
private _inkToTextStartX: number | undefined;
private _inkToTextStartY: number | undefined;
private _wordPalette: Map<string, string> = new Map<string, string>();
@@ -582,7 +580,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onPointerUp = (e: PointerEvent): void => {
- this._lastClientY = this._lastClientX = undefined;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
document.removeEventListener("pointermove", this.onPointerMove);
@@ -1152,16 +1149,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
- const handler = (e: any) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
-
- document.addEventListener("dashDragging", handler);
+ this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
componentWillUnmount() {
this._layoutComputeReaction?.();
-
- const handler = (e: any) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
- document.removeEventListener("dashDragging", handler);
+ this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
@@ -1176,39 +1169,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// <div ref={this._marqueeRef}>
@action
- handleDragging = (e: CustomEvent<React.DragEvent>, de: DragEvent) => {
- if ((e as any).handlePan) return;
+ onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
(e as any).handlePan = true;
- this._lastClientY = e.detail.clientY;
- this._lastClientX = e.detail.clientX;
if (this._marqueeRef?.current) {
const dragX = e.detail.clientX;
const dragY = e.detail.clientY;
const bounds = this._marqueeRef.current?.getBoundingClientRect();
- const deltaX = dragX - bounds.left < 25 ? -2 : bounds.right - dragX < 25 ? 2 : 0;
- const deltaY = dragY - bounds.top < 25 ? -2 : bounds.bottom - dragY < 25 ? 2 : 0;
- (deltaX !== 0 || deltaY !== 0) && this.continuePan(deltaX, deltaY);
+ const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
+ const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
+ if (deltaX !== 0 || deltaY !== 0) {
+ this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
+ this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+ }
}
e.stopPropagation();
}
- continuePan = (deltaX: number, deltaY: number) => {
- setTimeout(action(() => {
- const dragY = this._lastClientY;
- const dragX = this._lastClientX;
- if (dragY !== undefined && dragX !== undefined && this._marqueeRef.current) {
- const bounds = this._marqueeRef.current.getBoundingClientRect();
- this.Document._panY = NumCast(this.Document._panY) + deltaY;
- this.Document._panX = NumCast(this.Document._panX) + deltaX;
- if (dragY - bounds.top < 25 || bounds.bottom - dragY < 25 || dragX - bounds.left < 25 || bounds.right - dragX < 25) {
- this.continuePan(deltaX, deltaY);
- }
- } else this._lastClientY !== undefined && this._lastClientX !== undefined && this.continuePan(deltaX, deltaY);
- }), 50);
- }
-
promoteCollection = undoBatch(action(() => {
const childDocs = this.childDocs.slice();
childDocs.forEach(doc => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2db665337..8aab2e6a5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc";
+import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -276,7 +277,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAddonly || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
this.clearSelection();
}
});
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f1438fd54..f2f8ada68 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc";
+import { Doc, Opt, Field, AclPrivate } from "../../../fields/Doc";
import { Cast, StrCast, NumCast } from "../../../fields/Types";
import { OmitKeys, Without, emptyPath } from "../../../Utils";
import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox";
@@ -36,7 +36,7 @@ import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
import React = require("react");
import { RecommendationsBox } from "../RecommendationsBox";
-import { TraceMobx } from "../../../fields/util";
+import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
import { ScriptField } from "../../../fields/ScriptField";
import XRegExp = require("xregexp");
@@ -184,8 +184,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
const bindings = this.CreateBindings(onClick, onInput);
// layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-
- return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || this.layoutDoc[AclSym] === AclPrivate) ? (null) :
+ return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
<ObserverJsxParser
key={42}
blacklistedAttrs={[]}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c0a8b3a59..0b5bd707b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -12,7 +12,7 @@ import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -26,7 +26,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
import { Scripting } from '../../util/Scripting';
import { SearchUtil } from '../../util/SearchUtil';
import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager from '../../util/SharingManager';
+import SharingManager, { SharingPermissions } from '../../util/SharingManager';
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -699,7 +699,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+ setAcl = (acl: SharingPermissions) => {
this.dataDoc.ACL = this.props.Document.ACL = acl;
DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
@@ -707,9 +707,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
});
}
+
@undoBatch
@action
- testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+ testAcl = (acl: SharingPermissions) => {
this.dataDoc.author = this.props.Document.author = "ADMIN";
this.dataDoc.ACL = this.props.Document.ACL = acl;
DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
@@ -806,7 +807,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
}
- moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
@@ -819,14 +820,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// const existingAcls = cm.findByDescription("Privacy...");
// const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
- // aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
- // aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
- // aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
// !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+ // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
+
// const recommender_subitems: ContextMenuProps[] = [];
// recommender_subitems.push({
@@ -1184,7 +1187,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
render() {
if (!(this.props.Document instanceof Doc)) return (null);
- if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
+ if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null);
if (this.props.Document.hidden) return (null);
const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index fe0f067ad..5b85d8b0b 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -11,7 +11,6 @@
.fontIconBox-label {
background: gray;
color:white;
- margin-left: -10px;
border-radius: 8px;
width:100%;
position: absolute;
@@ -19,6 +18,8 @@
font-size: 8px;
margin-top:4px;
letter-spacing: normal;
+ left: 0;
+ overflow: hidden;
}
svg {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 30e1fa9cd..7906e2533 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import applyDevTools = require("prosemirror-dev-tools");
import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../../fields/Schema";
import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../../DocServer";
@@ -233,12 +233,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
let unchanged = true;
- if (!this.dataDoc[AclSym]) {
+ if (GetEffectiveAcl(this.dataDoc) === AclEdit) {
if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
this._applyingChange = true;
(curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if (json !== curLayout?.Data) {
+ if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
!curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
!curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 00c56d73e..6592c488b 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -47,7 +47,7 @@ export default class PDFMenu extends AntimodeMenu {
public AddTag: (key: string, value: string) => boolean = returnFalse;
public PinToPres: () => void = unimplementedFunction;
public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
- public get Active() { return this._opacity ? true : false; }
+ public get Active() { return this._left > 0; }
constructor(props: Readonly<{}>) {
super(props);