aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts12
-rw-r--r--src/client/DocServer.ts9
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts2
-rw-r--r--src/client/documents/DocumentTypes.ts4
-rw-r--r--src/client/documents/Documents.ts146
-rw-r--r--src/client/documents/Gitlike.ts36
-rw-r--r--src/client/goldenLayout.js48
-rw-r--r--src/client/util/CurrentUserUtils.ts150
-rw-r--r--src/client/util/DocumentManager.ts15
-rw-r--r--src/client/util/DragManager.ts9
-rw-r--r--src/client/util/GroupMemberView.tsx1
-rw-r--r--src/client/util/HypothesisUtils.ts2
-rw-r--r--src/client/util/LinkManager.ts70
-rw-r--r--src/client/util/SelectionManager.ts3
-rw-r--r--src/client/util/SettingsManager.tsx82
-rw-r--r--src/client/util/SharingManager.tsx93
-rw-r--r--src/client/views/.DS_Storebin10244 -> 10244 bytes
-rw-r--r--src/client/views/AntimodeMenu.scss4
-rw-r--r--src/client/views/DocComponent.tsx24
-rw-r--r--src/client/views/DocumentButtonBar.scss35
-rw-r--r--src/client/views/DocumentButtonBar.tsx35
-rw-r--r--src/client/views/DocumentDecorations.scss6
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/EditableView.scss8
-rw-r--r--src/client/views/InkControls.tsx71
-rw-r--r--src/client/views/InkHandles.tsx14
-rw-r--r--src/client/views/InkStrokeProperties.ts56
-rw-r--r--src/client/views/InkingStroke.tsx53
-rw-r--r--src/client/views/Main.tsx1
-rw-r--r--src/client/views/MainView.scss60
-rw-r--r--src/client/views/MainView.tsx84
-rw-r--r--src/client/views/MarqueeAnnotator.tsx14
-rw-r--r--src/client/views/PropertiesView.scss37
-rw-r--r--src/client/views/PropertiesView.tsx16
-rw-r--r--src/client/views/SidebarAnnos.tsx37
-rw-r--r--src/client/views/StyleProvider.tsx1
-rw-r--r--src/client/views/_nodeModuleOverrides.scss51
-rw-r--r--src/client/views/collections/CollectionDockingView.scss100
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx56
-rw-r--r--src/client/views/collections/CollectionLinearView.scss26
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx10
-rw-r--r--src/client/views/collections/CollectionMenu.scss4
-rw-r--r--src/client/views/collections/CollectionMenu.tsx9
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx4
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx35
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx14
-rw-r--r--src/client/views/collections/CollectionView.tsx11
-rw-r--r--src/client/views/collections/TabDocView.scss59
-rw-r--r--src/client/views/collections/TabDocView.tsx122
-rw-r--r--src/client/views/collections/TreeView.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx37
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx7
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx4
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx4
-rw-r--r--src/client/views/global/globalCssVariables.scss29
-rw-r--r--src/client/views/global/globalEnums.tsx4
-rw-r--r--src/client/views/linking/LinkEditor.scss8
-rw-r--r--src/client/views/linking/LinkMenu.scss26
-rw-r--r--src/client/views/linking/LinkMenu.tsx8
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx2
-rw-r--r--src/client/views/linking/LinkMenuItem.scss8
-rw-r--r--src/client/views/linking/LinkPopup.scss45
-rw-r--r--src/client/views/linking/LinkPopup.tsx113
-rw-r--r--src/client/views/nodes/AudioBox.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss55
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx158
-rw-r--r--src/client/views/nodes/DocumentView.scss2
-rw-r--r--src/client/views/nodes/DocumentView.tsx27
-rw-r--r--src/client/views/nodes/FieldView.tsx6
-rw-r--r--src/client/views/nodes/FontIconBox.tsx3
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.scss65
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.tsx112
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx27
-rw-r--r--src/client/views/nodes/VideoBox.tsx8
-rw-r--r--src/client/views/nodes/WebBox.tsx106
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx62
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts70
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx19
-rw-r--r--src/client/views/nodes/trails/PresBox.scss (renamed from src/client/views/nodes/PresBox.scss)6
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx (renamed from src/client/views/nodes/PresBox.tsx)184
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss (renamed from src/client/views/presentationview/PresElementBox.scss)0
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx (renamed from src/client/views/presentationview/PresElementBox.tsx)50
-rw-r--r--src/client/views/nodes/trails/PresEnums.ts28
-rw-r--r--src/client/views/nodes/trails/index.ts3
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx17
-rw-r--r--src/client/views/pdf/PDFViewer.tsx5
-rw-r--r--src/client/views/search/SearchBox.tsx2
-rw-r--r--src/client/views/topbar/TopBar.scss217
-rw-r--r--src/client/views/topbar/TopBar.tsx66
-rw-r--r--src/fields/Doc.ts106
-rw-r--r--src/fields/List.ts19
-rw-r--r--src/fields/URLField.ts15
-rw-r--r--src/fields/util.ts53
-rw-r--r--src/mobile/ImageUpload.tsx2
-rw-r--r--src/server/server_Initialization.ts3
-rw-r--r--src/server/websocket.ts2
102 files changed, 2274 insertions, 1287 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index ef9c51b8b..194c38a6f 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -67,7 +67,6 @@ export namespace Utils {
export function prepend(extension: string): string {
return window.location.origin + extension;
}
-
export function fileUrl(filename: string): string {
return prepend(`/files/${filename}`);
}
@@ -191,11 +190,12 @@ export namespace Utils {
return { h: h, s: s, l: l };
}
- export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number) {
- if (scrollTop + contextHgt < targetY + targetHgt * 1.1) {
- return Math.ceil(targetY + targetHgt * 1.1 - contextHgt);
- } else if (scrollTop > targetY - targetHgt * .1) {
- return Math.max(0, Math.floor(targetY - targetHgt * .1));
+ export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number) {
+ if (scrollTop + contextHgt < targetY + minSpacing + targetHgt) {
+ return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
+ }
+ if (scrollTop > targetY - minSpacing - targetHgt) {
+ return Math.max(0, Math.floor(targetY - minSpacing - targetHgt));
}
}
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 1d7497cf8..e498a7cca 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
import * as io from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, UpdatingFromServer } from '../fields/Doc';
+import { Opt, Doc, UpdatingFromServer, updateCachedAcls } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../fields/RefField';
@@ -61,6 +61,9 @@ export namespace DocServer {
DocServer.PlaygroundFields = livePlaygroundFields;
livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground));
}
+ export function IsPlaygroundField(field: string) {
+ return DocServer.PlaygroundFields?.includes(field.replace(/^_/, ""));
+ }
export function setFieldWriteMode(field: string, writeMode: WriteMode) {
fieldWriteModes[field] = writeMode;
@@ -225,7 +228,7 @@ export namespace DocServer {
* the server if the document has not been cached.
* @param id the id of the requested document
*/
- const _GetRefFieldImpl = (id: string, force: boolean = false): Promise<Opt<RefField>> => {
+ const _GetRefFieldImpl = async (id: string, force: boolean = false): Promise<Opt<RefField>> => {
// an initial pass through the cache to determine whether the document needs to be fetched,
// is already in the process of being fetched or already exists in the
// cache
@@ -395,7 +398,7 @@ export namespace DocServer {
(_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f);
} else if (field) {
proms.push(_cache[field.id] as any);
- fieldMap[field.id] = field;
+ fieldMap[field.id] = DocServer.GetCachedRefField(field.id) || field;
}
}
});
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 899e65a16..ff9460b62 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -285,7 +285,7 @@ export namespace GooglePhotos {
const photos = await endpoint();
const albumId = StrCast(collection.albumId);
if (albumId && albumId.length) {
- const enrichment = new photos.TextEnrichment(content || Utils.prepend("/doc/" + collection[Id]));
+ const enrichment = new photos.TextEnrichment(content || Doc.globalServerPath(collection));
const position = new photos.AlbumPosition(photos.AlbumPosition.POSITIONS.FIRST_IN_ALBUM);
const enrichmentItem = await photos.albums.addEnrichment(albumId, enrichment, position);
if (enrichmentItem) {
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 8565784b4..dba7ff907 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -18,7 +18,7 @@ export enum DocumentType {
LABEL = "label", // simple text label
BUTTON = "button", // onClick button
WEBCAM = "webcam", // webcam
- HTMLANCHOR = "htmlanchor", // text selection anchor in PDF/Web
+ MARKER = "marker", // generic marker document not intended to be viewed independently of its context (e.g., for text selections in PDF/Web/RTF)
DATE = "date", // calendar view of a date
SCRIPTING = "script", // script editor
EQUATION = "equation", // equation editor
@@ -40,6 +40,4 @@ export enum DocumentType {
LINKDB = "linkdb", // database of links ??? why do we have this
SCRIPTDB = "scriptdb", // database of scripts
GROUPDB = "groupdb", // database of groups
-
- TEXTANCHOR = "textanchor" // selection of text in a text box
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f1db3e32c..48886aa3b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,7 @@
import { action, runInAction } from "mobx";
import { basename, extname } from "path";
import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing, updateCachedAcls } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { HtmlField } from "../../fields/HtmlField";
import { InkField } from "../../fields/InkField";
@@ -45,14 +45,14 @@ import { LabelBox } from "../views/nodes/LabelBox";
import { LinkBox } from "../views/nodes/LinkBox";
import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
import { PDFBox } from "../views/nodes/PDFBox";
-import { PresBox } from "../views/nodes/PresBox";
+import { PresBox } from "../views/nodes/trails/PresBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ScriptingBox } from "../views/nodes/ScriptingBox";
import { SliderBox } from "../views/nodes/SliderBox";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
-import { PresElementBox } from "../views/presentationview/PresElementBox";
+import { PresElementBox } from "../views/nodes/trails/PresElementBox";
import { SearchBox } from "../views/search/SearchBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { DocumentType } from "./DocumentTypes";
@@ -427,7 +427,7 @@ export namespace Docs {
[DocumentType.PRESELEMENT, {
layout: { view: PresElementBox, dataField: defaultDataKey }
}],
- [DocumentType.HTMLANCHOR, {
+ [DocumentType.MARKER, {
layout: { view: CollectionView, dataField: defaultDataKey },
options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
}],
@@ -452,10 +452,6 @@ export namespace Docs {
layout: { view: EmptyBox, dataField: defaultDataKey },
options: { links: ComputedField.MakeFunction("links(self)") as any }
}],
- [DocumentType.TEXTANCHOR, {
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { targetDropAction: "move", links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
- }]
]);
const suffix = "Proto";
@@ -554,84 +550,6 @@ export namespace Docs {
export namespace Create {
/**
- * Synchronously returns a collection into which
- * the device documents will be put. This is initially empty,
- * but gets populated by updates from the web socket. When everything is over,
- * this function cleans up after itself.
- * s
- * Look at Websocket.ts for the server-side counterpart to this
- * function.
- */
- export function Buxton() {
- let responded = false;
- const loading = new Doc;
- loading.title = "Please wait for the import script...";
- const parent = TreeDocument([loading], {
- title: "The Buxton Collection",
- _width: 400,
- _height: 400
- });
- const parentProto = Doc.GetProto(parent);
- const { _socket } = DocServer;
-
- // just in case, clean up
- _socket.off(MessageStore.BuxtonDocumentResult.Message);
- _socket.off(MessageStore.BuxtonImportComplete.Message);
-
- // this is where the client handles the receipt of a new valid parsed document
- Utils.AddServerHandler(_socket, MessageStore.BuxtonDocumentResult, ({ device, invalid: errors }) => {
- if (!responded) {
- responded = true;
- parentProto.data = new List<Doc>();
- }
- if (device) {
- const { title, __images, additionalMedia } = device;
- delete device.__images;
- delete device.additionalMedia;
- const { ImageDocument, StackingDocument } = Docs.Create;
- const constructed = __images.map(({ url, nativeWidth, nativeHeight }) => ({ url: Utils.prepend(url), nativeWidth, nativeHeight }));
- const deviceImages = constructed.map(({ url, nativeWidth, nativeHeight }, i) => {
- const imageDoc = ImageDocument(url, {
- title: `image${i}.${extname(url)}`,
- _nativeWidth: nativeWidth,
- _nativeHeight: nativeHeight
- });
- const media = additionalMedia[i];
- if (media) {
- for (const key of Object.keys(media)) {
- imageDoc[`additionalMedia_${key}`] = Utils.prepend(`/files/${key}/buxton/${media[key]}`);
- }
- }
- return imageDoc;
- });
- // the main document we create
- const doc = StackingDocument(deviceImages, { title, hero: new ImageField(constructed[0].url) });
- doc.nameAliases = new List<string>([title.toLowerCase()]);
- // add the parsed attributes to this main document
- Doc.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
- Doc.AddDocToList(parentProto, "data", doc);
- } else if (errors) {
- console.log("Documents:" + errors);
- } else {
- alert("A Buxton document import was completely empty (??)");
- }
- });
-
- // when the import is complete, we stop listening for these creation
- // and termination events and alert the user
- Utils.AddServerHandler(_socket, MessageStore.BuxtonImportComplete, ({ deviceCount, errorCount }) => {
- _socket.off(MessageStore.BuxtonDocumentResult.Message);
- _socket.off(MessageStore.BuxtonImportComplete.Message);
- alert(`Successfully imported ${deviceCount} device${deviceCount === 1 ? "" : "s"}, with ${errorCount} error${errorCount === 1 ? "" : "s"}, in ${(Date.now() - startTime) / 1000} seconds.`);
- });
- const startTime = Date.now();
- Utils.Emit(_socket, MessageStore.BeginBuxtonImport, ""); // signal the server to start importing
- return parent; // synchronously return the collection, to be populateds
- }
-
- Scripting.addGlobal(Buxton);
-
- /**
* This function receives the relevant document prototype and uses
* it to create a new of that base-level prototype, or the
* underlying data document, which it then delegates again
@@ -659,8 +577,10 @@ export namespace Docs {
dataProps.creationDate = new DateField;
dataProps[`${fieldKey}-lastModified`] = new DateField;
dataProps["acl-Override"] = "None";
- dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+ dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+
dataProps[fieldKey] = data;
+
// so that the list of annotations is already initialised, prevents issues in addonly.
// without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
dataProps[fieldKey + "-annotations"] = new List<Doc>();
@@ -668,18 +588,20 @@ export namespace Docs {
viewProps.author = Doc.CurrentUserEmail;
viewProps["acl-Override"] = "None";
- viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+ viewProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
- ![DocumentType.LINK, DocumentType.TEXTANCHOR, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
+ ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
- !Doc.IsSystem(dataDoc) && ![DocumentType.HTMLANCHOR, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR, DocumentType.TEXTANCHOR].includes(proto.type as any) &&
+ !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
!dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc);
+ updateCachedAcls(dataDoc);
+ updateCachedAcls(viewDoc);
return viewDoc;
}
export function ImageDocument(url: string, options: DocumentOptions = {}) {
- const imgField = new ImageField(new URL(url));
+ const imgField = new ImageField(url);
return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: path.basename(url), ...options });
}
@@ -693,11 +615,11 @@ export namespace Docs {
}
export function VideoDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(new URL(url)), options);
+ return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options);
}
export function YoutubeDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(new URL(url)), options);
+ return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options);
}
export function WebCamDocument(url: string, options: DocumentOptions = {}) {
@@ -713,7 +635,7 @@ export namespace Docs {
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)),
+ return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url),
{ ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any });
}
@@ -779,18 +701,18 @@ export namespace Docs {
I.author = Doc.CurrentUserEmail;
I.rotation = 0;
I.data = new InkField(points);
- I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+ I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
I["acl-Override"] = "None";
I[Initializing] = false;
return I;
}
export function PdfDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), options);
+ return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options);
}
export function WebDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, options);
+ return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(url) : undefined, options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -801,17 +723,22 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options });
}
- export function TextanchorDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, options, id);
- }
-
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Freeform }, id);
documents.map(d => d.context = inst);
return inst;
}
+
+ export function WebanchorDocument(url?: string, options: DocumentOptions = {}, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), url, options, id);
+ }
+
+ export function TextanchorDocument(options: DocumentOptions = {}, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
+ }
+
export function HTMLAnchorDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.HTMLANCHOR), new List(documents), options, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id);
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -838,8 +765,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaHeaders: new List(schemaHeaders), ...options, _viewType: CollectionViewType.Schema });
}
- export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Tree }, id);
+ export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Tree }, id, undefined, protoId);
}
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
@@ -889,8 +816,8 @@ export namespace Docs {
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
- const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true });
- const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true });
+ const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true });
+ const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true });
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
@@ -1113,8 +1040,8 @@ export namespace DocUtils {
title: ComputedField.MakeFunction("generateLinkTitle(self)") as any,
"anchor1-useLinkSmallAnchor": source.doc.useLinkSmallAnchor ? true : undefined,
"anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined,
- "acl-Public": SharingPermissions.Add,
- "_acl-Public": SharingPermissions.Add,
+ "acl-Public": SharingPermissions.Augment,
+ "_acl-Public": SharingPermissions.Augment,
layout_linkView: Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null),
linkDisplay: true, hidden: true,
linkRelationship,
@@ -1432,4 +1359,7 @@ Scripting.addGlobal(function generateLinkTitle(self: Doc) {
const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : "<?>";
const relation = self.linkRelationship || "to";
return `${anchor1title} (${relation}) ${anchor2title}`;
+});
+Scripting.addGlobal(function openTabAlias(tab: Doc) {
+ CollectionDockingView.AddSplit(Doc.MakeAlias(tab), "right");
}); \ No newline at end of file
diff --git a/src/client/documents/Gitlike.ts b/src/client/documents/Gitlike.ts
index fddf317bc..575c984f5 100644
--- a/src/client/documents/Gitlike.ts
+++ b/src/client/documents/Gitlike.ts
@@ -1,7 +1,8 @@
-import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc";
import { List } from "../../fields/List";
-import { ObjectField } from "../../fields/ObjectField";
import { Cast, DateCast } from "../../fields/Types";
+import { DateField } from "../../fields/DateField";
+import { Id } from "../../fields/FieldSymbols";
// synchs matching documents on the two branches that are being merged/pulled
// currently this just synchs the main 'fieldKey' component of the data since
@@ -10,11 +11,22 @@ function GitlikeSynchDocs(bd: Doc, md: Doc) {
const fieldKey = Doc.LayoutFieldKey(md);
const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
- if (bdate === mdate || bdate > mdate) return;
const bdproto = bd && Doc.GetProto(bd);
+ if (bdate !== mdate && bdate <= mdate) {
+ if (bdproto && md) {
+ bdproto[fieldKey] = Field.Copy(md[fieldKey]);
+ bdproto[`${fieldKey}-lastModified`] = new DateField();
+ }
+ }
+ const bldate = DateCast(bd._lastModified)?.date;
+ const mldate = DateCast(md._lastModified)?.date;
+ if (bldate === mldate || bldate > mldate) return;
if (bdproto && md) {
- bdproto[fieldKey] = ObjectField.MakeCopy(md[fieldKey] as ObjectField);
- bdproto[`${fieldKey}-lastModified`] = ObjectField.MakeCopy(md[`${fieldKey}-lastModified`] as ObjectField);
+ bd.x = Field.Copy(md.x);
+ bd.y = Field.Copy(md.y);
+ bd.width = Field.Copy(md.width);
+ bd.height = Field.Copy(md.height);
+ bdproto._lastModified = new DateField();
}
}
@@ -36,8 +48,9 @@ async function GitlikePullFromMaster(branch: Doc, suffix = "") {
const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
bd && GitlikeSynchDocs(bd, md);
});
+ const cloneMap = new Map<string, Doc>(); cloneMap.set(masterMain[Id], branch);
// make branch clones of them, then add them to the branch
- const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
+ const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []);
newlyBranchedDocs.forEach(nd => {
Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
nd.context = branch;
@@ -56,22 +69,26 @@ async function GitlikeMergeWithMaster(master: Doc, suffix = "") {
branches?.map(async branch => {
const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
branchChildren && await Promise.all(branchChildren.map(async bd => {
+ const cloneMap = new Map<string, Doc>(); cloneMap.set(master[Id], branch);
// see if the branch's child exists on master.
- const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true)).clone;
+ const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone;
// if the branch's child didn't exist on master, we make a branch clone of the child to add to master.
// however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
// on the branch child and master clone.
if (masterChild.branchOf) {
const branchDocProto = Doc.GetProto(bd);
const masterChildProto = Doc.GetProto(masterChild);
- masterChildProto.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
+ const branchTitle = bd.title;
+ branchDocProto.title = masterChildProto.title;
+ masterChildProto.title = branchTitle;
+ masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto); // the branch child should not have the master child in its branch list.
branchDocProto.branchOf = masterChild; // the branch child is now a branch of the master child
}
Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
masterChild.context = master;
- GitlikeSynchDocs(Doc.GetProto(masterChild), bd);
+ GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd);
}));
const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
masterChildren?.forEach(mc => { // see if any master children
@@ -93,6 +110,7 @@ export async function BranchTask(target: Doc, action: "pull" | "merge") {
const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster;
await func(target, "");
await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations"));
+ await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar"));
}
export async function BranchCreate(target: Doc) {
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 7fca8d9d5..db94cce3d 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -1567,15 +1567,15 @@
showCloseIcon: true,
responsiveMode: 'onload', // Can be onload, always, or none.
tabOverlapAllowance: 0, // maximum pixel overlap per tab
- reorderOnTabMenuClick: true,
+ reorderOnTabMenuClick: false, //do not reorder! - horizontal scroll
tabControlOffset: 10
},
dimensions: {
- borderWidth: 5,
+ borderWidth: 3,
borderGrabWidth: 5,
minItemHeight: 10,
- minItemWidth: 10,
- headerHeight: 20,
+ minItemWidth: 20,
+ headerHeight: 27,
dragProxyWidth: 300,
dragProxyHeight: 200
},
@@ -2390,11 +2390,11 @@
this._createControls();
};
+ // '<ul class="lm_tabdropdown_list"></ul>',
lm.controls.Header._template = [
'<div class="lm_header">',
'<ul class="lm_tabs"></ul>',
'<ul class="lm_controls"></ul>',
- '<ul class="lm_tabdropdown_list"></ul>',
'</div>'
].join('');
@@ -2477,20 +2477,21 @@
}
}
- if (this.layoutManager.config.settings.reorderOnTabMenuClick) {
- /**
- * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first.
- * This will make sure the most used tabs stay visible.
- */
- if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) {
- activeTab = this.tabs[this.parent.config.activeItemIndex];
- for (j = this.parent.config.activeItemIndex; j > 0; j--) {
- this.tabs[j] = this.tabs[j - 1];
- }
- this.tabs[0] = activeTab;
- this.parent.config.activeItemIndex = 0;
- }
- }
+ // glr: removed for new tab manager
+ // if (this.layoutManager.config.settings.reorderOnTabMenuClick) {
+ // /**
+ // * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first.
+ // * This will make sure the most used tabs stay visible.
+ // */
+ // if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) {
+ // activeTab = this.tabs[this.parent.config.activeItemIndex];
+ // for (j = this.parent.config.activeItemIndex; j > 0; j--) {
+ // this.tabs[j] = this.tabs[j - 1];
+ // }
+ // this.tabs[0] = activeTab;
+ // this.parent.config.activeItemIndex = 0;
+ // }
+ // }
this._updateTabSizes();
this.parent.emitBubblingEvent('stateChanged');
@@ -2578,8 +2579,8 @@
*/
showTabDropdown = lm.utils.fnBind(this._showAdditionalTabsDropdown, this);
tabDropdownLabel = this.layoutManager.config.labels.tabDropdown;
- this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown);
- this.tabDropdownButton.element.hide();
+ // this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown);
+ // this.tabDropdownButton.element.hide();
/**
* Popout control to launch component in new window.
@@ -2678,9 +2679,6 @@
return;
}
- //Show the menu based on function argument
- this.tabDropdownButton.element.toggle(showTabMenu === true);
-
var size = function (val) {
return val ? 'width' : 'height';
};
@@ -4875,7 +4873,7 @@
this.layoutManager.dropTargetIndicator.highlightArea({
x1: headerOffset.left,
x2: headerOffset.left + 100,
- y1: headerOffset.top + this.header.element.height() - 20,
+ y1: headerOffset.top + this.header.element.height() - 25,
y2: headerOffset.top + this.header.element.height()
});
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 22504f102..34990e121 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,14 +1,15 @@
-import { computed, observable, reaction, action } from "mobx";
+import { computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
-import { DataSym, Doc, DocListCast, DocListCastAsync, AclReadonly } from "../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
+import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { SharingPermissions } from "../../fields/util";
import { Utils } from "../../Utils";
@@ -19,6 +20,7 @@ import { Networking } from "../Network";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
import { LabelBox } from "../views/nodes/LabelBox";
@@ -31,10 +33,10 @@ import { LinkManager } from "./LinkManager";
import { Scripting } from "./Scripting";
import { SearchUtil } from "./SearchUtil";
import { SelectionManager } from "./SelectionManager";
-import { UndoManager } from "./UndoManager";
+import { ColorScheme } from "./SettingsManager";
+import { SharingManager } from "./SharingManager";
import { SnappingManager } from "./SnappingManager";
-import { InkTool } from "../../fields/InkField";
-import { computedFn } from "mobx-utils";
+import { UndoManager } from "./UndoManager";
export let resolvedPorts: { server: number, socket: number };
@@ -456,7 +458,7 @@ export class CurrentUserUtils {
((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, isTemplateDoc: true, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List<string>(["system"]) });
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List<string>(["system"]) });
}
if (doc.activeMobileMenu === undefined) {
this.setupActiveMobileMenu(doc);
@@ -472,7 +474,7 @@ export class CurrentUserUtils {
{ toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc },
{ toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
{ toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc },
- { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
+ // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
{ toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc },
{ toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
{ toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc },
@@ -532,10 +534,9 @@ export class CurrentUserUtils {
{ title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
{ title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' },
{ title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
- // { title: "Filter", target: Cast(doc.currentFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' },
{ title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
- { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' },
- { title: "Settings", target: undefined as any, icon: "cog", click: 'selectMainMenu(self)' },
+ // { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' },
+ // { title: "Settings", target: undefined as any, icon: "cog", click: 'selectMainMenu(self)' },
{ title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)' },
];
}
@@ -561,7 +562,6 @@ export class CurrentUserUtils {
dontUndo: true,
title,
target,
- backgroundColor: "black",
_dropAction: "alias",
_removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
_width: 60,
@@ -576,8 +576,10 @@ export class CurrentUserUtils {
title: "menuItemPanel",
childDropAction: "alias",
_chromeHidden: true,
+ backgroundColor: Colors.DARK_GRAY,
+ boxShadow: "rgba(0,0,0,0)",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
- backgroundColor: "black", ignoreClick: true,
+ ignoreClick: true,
_gridGap: 0,
_yMargin: 0,
_yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
@@ -587,8 +589,6 @@ export class CurrentUserUtils {
PromiseValue(Cast(doc.menuStack, Doc)).then(stack => {
stack && PromiseValue(stack.data).then(btns => {
DocListCastAsync(btns).then(bts => bts?.forEach(btn => {
- btn.color = "white";
- btn._backgroundColor = "";
btn.dontUndo = true;
btn.system = true;
if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files
@@ -747,7 +747,7 @@ export class CurrentUserUtils {
if (doc.myTools === undefined) {
const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
+ system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0",
})) as any as Doc;
doc.myTools = toolsStack;
@@ -759,10 +759,10 @@ export class CurrentUserUtils {
await doc.myDashboards;
if (doc.myDashboards === undefined) {
doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "My Dashboards", _height: 400, childHideLinkButton: true,
+ title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
+ _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true
}));
const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
(doc.myDashboards as any as Doc).contextMenuScripts = new List<ScriptField>([newDashboard!]);
@@ -775,7 +775,7 @@ export class CurrentUserUtils {
await doc.myPresentations;
if (doc.myPresentations === undefined) {
doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "My Presentations", _height: 100,
+ title: "My Trails", _showTitle: "title", _height: 100,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
@@ -794,7 +794,7 @@ export class CurrentUserUtils {
doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true });
doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileRoot as Doc, doc.myFileOrphans as Doc], {
- title: "My Documents", _height: 100,
+ title: "My Documents", _showTitle: "title", _height: 100,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
@@ -808,7 +808,7 @@ export class CurrentUserUtils {
// setup Recently Closed library item
if (doc.myRecentlyClosedDocs === undefined) {
doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "Recently Closed", treeViewShowClearButton: true, childHideLinkButton: true,
+ title: "Recently Closed", _showTitle: "title", treeViewShowClearButton: true, childHideLinkButton: true,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
@@ -839,7 +839,7 @@ export class CurrentUserUtils {
doc.treeViewOpen = true;
doc.treeViewExpandedView = "fields";
doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], {
- treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc",
+ treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
})) as any as Doc;
@@ -859,11 +859,11 @@ export class CurrentUserUtils {
static async setupSidebarButtons(doc: Doc) {
CurrentUserUtils.setupSidebarContainer(doc);
await CurrentUserUtils.setupToolsBtnPanel(doc);
+ CurrentUserUtils.setupImportSidebar(doc);
CurrentUserUtils.setupDashboards(doc);
CurrentUserUtils.setupPresentations(doc);
CurrentUserUtils.setupFilesystem(doc);
CurrentUserUtils.setupRecentlyClosedDocs(doc);
- // CurrentUserUtils.setupFilterDocs(doc);
CurrentUserUtils.setupUserDoc(doc);
}
@@ -891,6 +891,7 @@ export class CurrentUserUtils {
(doc["dockedBtn-undo"] as Doc).dontUndo = true;
(doc["dockedBtn-redo"] as Doc).dontUndo = true;
}
+
// sets up the default set of documents to be shown in the Overlay layer
static setupOverlays(doc: Doc) {
if (doc.myOverlayDocs === undefined) {
@@ -913,20 +914,22 @@ export class CurrentUserUtils {
let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
if (!linkDocs) {
linkDocs = new Doc(linkDatabaseId, true);
+ (linkDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail;
(linkDocs as Doc).author = Doc.CurrentUserEmail;
(linkDocs as Doc).data = new List<Doc>([]);
- (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add;
+ (linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
}
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
if (doc.mySharedDocs === undefined) {
let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer");
if (!sharedDocs) {
- sharedDocs = Docs.Create.StackingDocument([], {
- title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
- _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add, _chromeHidden: true,
+ sharedDocs = Docs.Create.TreeDocument([], {
+ title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
+ _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
+ _chromeHidden: true, boxShadow: "0 0",
}, sharingDocumentId + "outer", sharingDocumentId);
- (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Add;
+ (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
}
if (sharedDocs instanceof Doc) {
Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
@@ -939,14 +942,14 @@ export class CurrentUserUtils {
static setupImportSidebar(doc: Doc) {
if (doc.myImportDocs === undefined) {
doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
- title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
+ title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true, _chromeHidden: true,
}));
}
if (doc.myImportPanel === undefined) {
const uploads = Cast(doc.myImportDocs, Doc, null);
const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true });
- doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true }));
+ doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, _showTitle: "title", ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true, boxShadow: "0 0" }));
}
}
@@ -1007,10 +1010,14 @@ export class CurrentUserUtils {
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
+ // Document properties on load
doc.system = true;
+ doc.darkScheme = ColorScheme.Dark;
doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
doc.title = Doc.CurrentUserEmail;
doc._raiseWhenDragged = true;
+ doc._showLabel = false;
+ doc._showMenuLabel = true;
doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
doc.activeInkWidth = StrCast(doc.activeInkWidth, "1");
doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
@@ -1022,7 +1029,7 @@ export class CurrentUserUtils {
doc.fontFamily = StrCast(doc.fontFamily, "Arial");
doc.fontColor = StrCast(doc.fontColor, "black");
doc.fontHighlight = StrCast(doc.fontHighlight, "");
- doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, true);
+ doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white");
doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
doc.noviceMode = BoolCast(doc.noviceMode, true);
@@ -1199,14 +1206,37 @@ export class CurrentUserUtils {
};
const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
+ freeformDoc.context = dashboardDoc;
+
+ // switching the tabs from the datadoc to the regular doc
+ const dashboardTabs = dashboardDoc[DataSym].data;
+ dashboardDoc[DataSym].data = new List<Doc>();
+ dashboardDoc.data = dashboardTabs;
+
+ // collating all docs on the dashboard to make a data-all field
+ const allDocs = new List<Doc>();
+ const allDocs2 = new List<Doc>(); // Array.from, spread, splice all cause so stack or acl issues for some reason
+ DocListCast(dashboardTabs).forEach(doc => {
+ const tabDocs = DocListCast(doc.data);
+ allDocs.push(...tabDocs);
+ allDocs2.push(...tabDocs);
+ });
+ dashboardDoc[DataSym]["data-all"] = allDocs;
+ dashboardDoc["data-all"] = allDocs2;
+ DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc);
+ DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
+
Doc.AddDocToList(myPresentations, "data", presentation);
userDoc.activePresentation = presentation;
- const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+ const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
const createDashboard = ScriptField.MakeScript(`createNewDashboard()`);
- dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!]);
- dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]);
+ const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
+ const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
+ const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
+ dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!, shareDashboard!, addToDashboards!, removeDashboard!]);
+ dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard", "Share Dashboard", "Add to Dashboards", "Remove Dashboard"]);
Doc.AddDocToList(dashboards, "data", dashboardDoc);
CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
@@ -1258,5 +1288,53 @@ Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Insta
"returns all the links to the document or its annotations", "(doc: any)");
Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); },
"imports files from device directly into the import sidebar");
-Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; },
- "toggle between regular rendeing and an informal sketch/comic style");
+Scripting.addGlobal(function shareDashboard(dashboard: Doc) {
+ SharingManager.Instance.open(undefined, dashboard);
+},
+ "opens sharing dialog for Dashboard");
+Scripting.addGlobal(async function removeDashboard(dashboard: Doc) {
+ const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data);
+ if (dashboards && dashboards.length > 1) {
+ if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboards.find(doc => doc !== dashboard)!);
+ Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard);
+ }
+},
+ "Remove Dashboard from Dashboards");
+Scripting.addGlobal(async function addToDashboards(dashboard: Doc) {
+ const dashboardAlias = Doc.MakeAlias(dashboard);
+
+ const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
+
+ // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly
+ dashboard["data-all"] = new List<Doc>(allDocs);
+ dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
+
+ const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
+ dockingConfig.content = [];
+ dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
+
+
+ dashboardAlias.data = new List<Doc>(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
+ DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
+ DocListCast(dashboardAlias.data)[0].data = new List<Doc>();
+ DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
+ Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
+ CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias);
+},
+ "adds Dashboard to set of Dashboards");
+
+/**
+ * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard.
+ */
+Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) {
+ if (dashboard[DataSym] instanceof Doc) {
+ const allDocs = DocListCast(dashboard["data-all"]);
+ const onScreenTab = DocListCast(dashboard.data)[0];
+ const onScreenDocs = DocListCast(onScreenTab.data);
+ return new List<Doc>(allDocs.reduce((result: Doc[], doc) => {
+ !onScreenDocs.includes(doc) && !onScreenDocs.includes(doc.aliasOf as Doc) && (result.push(doc));
+ return result;
+ }, []));
+ }
+ return [];
+});
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 304215a8f..cb0ee411c 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -28,7 +28,7 @@ export class DocumentManager {
DocListCast(view.rootDoc.links).forEach(link => {
const whichOtherAnchor = view.props.LayoutTemplateString?.includes("anchor2") ? "anchor1" : "anchor2";
const otherDoc = link && (link[whichOtherAnchor] as Doc);
- const otherDocAnno = otherDoc?.type === DocumentType.TEXTANCHOR ? otherDoc.annotationOn as Doc : undefined;
+ const otherDocAnno = DocumentType.MARKER === otherDoc?.type ? otherDoc.annotationOn as Doc : undefined;
otherDoc && DocumentManager.Instance.DocumentViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, otherDoc) || Doc.AreProtosEqual(dv.rootDoc, otherDocAnno)).
forEach(otherView => {
if (otherView.rootDoc.type !== DocumentType.LINK || otherView.props.LayoutTemplateString !== view.props.LayoutTemplateString) {
@@ -144,9 +144,11 @@ export class DocumentManager {
originalTarget = originalTarget ?? targetDoc;
const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
const docView = getFirstDocView(targetDoc, originatingDoc);
+ const wasHidden = targetDoc.hidden; //
+ if (wasHidden) runInAction(() => targetDoc.hidden = false); // if the target is hidden, un-hide it here.
const focusAndFinish = (didFocus: boolean) => {
if (originatingDoc?.isPushpin) {
- if (!didFocus || targetDoc.hidden) {
+ if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
targetDoc.hidden = !targetDoc.hidden;
}
} else {
@@ -161,13 +163,14 @@ export class DocumentManager {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
const targetDocContext = contextDoc || annotatedDoc;
- const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
- const focusView = !docView && targetDoc.type === DocumentType.TEXTANCHOR && annoContainerView ? annoContainerView : docView;
- if (!docView && annoContainerView && !focusView) {
+ const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
+ (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
+ const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
+ if (!docView && annoContainerView) {
annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
}
if (focusView) {
- focusView && Doc.linkFollowHighlight(focusView.rootDoc);
+ Doc.linkFollowHighlight(focusView.rootDoc);
focusView.focus(targetDoc, {
originalTarget, willZoom, afterFocus: (didFocus: boolean) =>
new Promise<ViewAdjustment>(res => {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c4842e88a..5e16de617 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -15,6 +15,15 @@ import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
export type dropActionType = "alias" | "copy" | "move" | "same" | "proto" | "none" | undefined; // undefined = move, "same" = move but don't call removeDropProperties
+
+/**
+ * Initialize drag
+ * @param _reference: The HTMLElement that is being dragged
+ * @param docFunc: The Dash document being moved
+ * @param moveFunc: The function called when the document is moved
+ * @param dropAction: What to do with the document when it is dropped
+ * @param dragStarted: Method to call when the drag is started
+ */
export function SetupDrag(
_reference: React.RefObject<HTMLElement>,
docFunc: () => Doc | Promise<Doc> | undefined,
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 927200ed3..b7f89794d 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -50,7 +50,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.props.group, (selectedOption as UserOptions).value)}
placeholder={"Add members"}
value={null}
- closeMenuOnSelect={true}
styles={{
dropdownIndicator: (base, state) => ({
...base,
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 8ddfce772..635673025 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -126,7 +126,7 @@ export namespace Hypothesis {
});
const annotationId = StrCast(linkDoc.annotationId);
- const linkUrl = Utils.prepend("/doc/" + sourceDoc[Id]);
+ const linkUrl = Doc.globalServerPath(sourceDoc);
const interval = setInterval(() => {// keep trying to edit until annotations have loaded and editing is successful
!success && document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", {
detail: { targetUrl: linkUrl, id: annotationId },
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 08f4ac9b7..933b98a8c 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { observable, observe, action } from "mobx";
+import { observable, observe, action, reaction, computed } from "mobx";
import { computedFn } from "mobx-utils";
import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
@@ -26,36 +26,41 @@ type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => vo
export class LinkManager {
@observable static _instance: LinkManager;
- @observable static userDocs: Doc[] = [];
+ @observable static userLinkDBs: Doc[] = [];
public static currentLink: Opt<Doc>;
public static get Instance() { return LinkManager._instance; }
+ public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb);
+ static links: Doc[] = [];
constructor() {
LinkManager._instance = this;
setTimeout(() => {
- LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
- const addLinkToDoc = action((link: Doc): any => {
+ LinkManager.userLinkDBs = [];
+ const addLinkToDoc = (link: Doc) => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
- if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => addLinkToDoc(link)));
- if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
- Doc.GetProto(a1)[DirectLinksSym].add(link);
- Doc.GetProto(a2)[DirectLinksSym].add(link);
- Doc.GetProto(link)[DirectLinksSym].add(link);
- }
- });
- const remLinkFromDoc = action((link: Doc): any => {
+ Promise.all([a1, a2]).then(action(() => {
+ if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+ Doc.GetProto(a1)[DirectLinksSym].add(link);
+ Doc.GetProto(a2)[DirectLinksSym].add(link);
+ Doc.GetProto(link)[DirectLinksSym].add(link);
+ }
+ }));
+ }
+ const remLinkFromDoc = (link: Doc) => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
- if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => remLinkFromDoc(link)));
- if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
- Doc.GetProto(a1)[DirectLinksSym].delete(link);
- Doc.GetProto(a2)[DirectLinksSym].delete(link);
- Doc.GetProto(link)[DirectLinksSym].delete(link);
- }
- });
- const watchUserLinks = (userLinks: List<Doc>) => {
+ Promise.all([a1, a2]).then(action(() => {
+ if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+ Doc.GetProto(a1)[DirectLinksSym].delete(link);
+ Doc.GetProto(a2)[DirectLinksSym].delete(link);
+ Doc.GetProto(link)[DirectLinksSym].delete(link);
+ }
+ }));
+ }
+ const watchUserLinkDB = (userLinkDBDoc: Doc) => {
+ LinkManager.links.push(...DocListCast(userLinkDBDoc.data));
const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
- observe(userLinks, change => {
+ observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
switch (change.type as any) {
case "splice":
(change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
@@ -64,13 +69,29 @@ export class LinkManager {
case "update": //let oldValue = change.oldValue;
}
}, true);
+ observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link)
+ change => {
+ switch (change.type as any) {
+ case "update":
+ Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => {
+ const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length);
+ const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length);
+
+ const added = newDocs?.filter(link => !(oldDocs || []).includes(link));
+ const removed = oldDocs?.filter(link => !(newDocs || []).includes(link));
+ added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
+ removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
+ });
+ }
+ }, true);
};
- observe(LinkManager.userDocs, change => {
+ observe(LinkManager.userLinkDBs, change => {
switch (change.type as any) {
- case "splice": (change as any).added.forEach(watchUserLinks); break;
+ case "splice": (change as any).added.forEach(watchUserLinkDB); break;
case "update": //let oldValue = change.oldValue;
}
}, true);
+ LinkManager.addLinkDB(Doc.LinkDBDoc());
});
}
@@ -135,7 +156,8 @@ export class LinkManager {
const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation, followLoc);
docViewProps.addDocTab(doc, where);
setTimeout(() => {
- const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
+ const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
+ const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
if (targDocView) {
targDocView.props.focus(doc, {
willZoom: BoolCast(sourceDoc.followLinkZoom, false),
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 00f0894c7..dbcc49f3d 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,6 +4,7 @@ import { Doc, Opt } from "../../fields/Doc";
import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView";
import { CollectionViewType } from "../views/collections/CollectionView";
import { DocumentView } from "../views/nodes/DocumentView";
+import { DocumentType } from "../documents/DocumentTypes";
export namespace SelectionManager {
@@ -22,7 +23,7 @@ export namespace SelectionManager {
@action
SelectView(docView: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
- if (!manager.SelectedViews.get(docView)) {
+ if (!manager.SelectedViews.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
if (!ctrlPressed) {
this.DeselectAll();
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 777394b05..bd91db779 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -18,6 +18,12 @@ const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
+export enum ColorScheme {
+ Dark = "Dark",
+ Light = "Light",
+ System = "Match System"
+}
+
@observer
export class SettingsManager extends React.Component<{}> {
public static Instance: SettingsManager;
@@ -32,7 +38,7 @@ export class SettingsManager extends React.Component<{}> {
@observable activeTab = "Accounts";
@computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
-
+ @computed get colorScheme() { return Doc.UserDoc().colorScheme; }
constructor(props: {}) {
super(props);
@@ -69,6 +75,28 @@ export class SettingsManager extends React.Component<{}> {
else DocServer.Control.makeEditable();
});
+ @undoBatch
+ @action
+ changeColorScheme = action((e: React.ChangeEvent) => {
+ const scheme: ColorScheme = (e.currentTarget as any).value;
+ switch (scheme) {
+ case ColorScheme.Light:
+ Doc.UserDoc().colorScheme = ColorScheme.Light;
+ addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
+ break;
+ case ColorScheme.Dark:
+ Doc.UserDoc().colorScheme = ColorScheme.Dark;
+ addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
+ break;
+ case ColorScheme.System: default:
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
+ Doc.UserDoc().colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light;
+ });
+ break;
+ }
+ });
+
+
@computed get colorsContent() {
const colorBox = (func: (color: ColorState) => void) => <SketchPicker onChange={func} color={StrCast(this.backgroundColor)}
presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
@@ -90,8 +118,7 @@ export class SettingsManager extends React.Component<{}> {
</Flyout>
</div>;
- const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
- const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
+ const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
return <div className="colors-content">
<div className="preferences-color">
@@ -102,14 +129,11 @@ export class SettingsManager extends React.Component<{}> {
<div className="preferences-color-text">Border/Header Color</div>
{userColorFlyout}
</div>
- <div className="preferences-font">
- <div className="preferences-font-text">Default Font</div>
- <div className="preferences-font-controls">
- <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
- {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
- </select>
- <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
- {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
+ <div className="preferences-colorScheme">
+ <div className="preferences-color-text">Color Scheme</div>
+ <div className="preferences-color-controls">
+ <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.UserDoc().colorScheme)}>
+ {colorSchemes.map(scheme => <option key={scheme} value={scheme}> {scheme} </option>)}
</select>
</div>
</div>
@@ -132,6 +156,16 @@ export class SettingsManager extends React.Component<{}> {
checked={BoolCast(Doc.UserDoc()._raiseWhenDragged)} />
<div className="preferences-check">Raise on drag</div>
</div>
+ <div>
+ <input type="checkbox" onChange={e => Doc.UserDoc()._showLabel = !Doc.UserDoc()._showLabel}
+ checked={BoolCast(Doc.UserDoc()._showLabel)} />
+ <div className="preferences-check">Show tool button labels</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => Doc.UserDoc()._showMenuLabel = !Doc.UserDoc()._showMenuLabel}
+ checked={BoolCast(Doc.UserDoc()._showMenuLabel)} />
+ <div className="preferences-check">Show menu button labels</div>
+ </div>
</div>;
}
@@ -149,6 +183,27 @@ export class SettingsManager extends React.Component<{}> {
</div>;
}
+ @computed get textContent() {
+
+ const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Roboto"];
+ const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
+
+ return (
+ <div className="tab-content appearances-content">
+ <div className="preferences-font">
+ <div className="preferences-font-text">Default Font</div>
+ <div className="preferences-font-controls">
+ <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
+ {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
+ </select>
+ <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
+ {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
+ </select>
+ </div>
+ </div>
+ </div>);
+ }
+
@action
changeVal = (e: React.ChangeEvent, pass: string) => {
const value = (e.target as any).value;
@@ -213,7 +268,8 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
<button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
<div className="default-acl">
- <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)} onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
+ <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)}
+ onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
<div className="acl-text">Default access private</div>
</div>
</div>
@@ -228,7 +284,7 @@ export class SettingsManager extends React.Component<{}> {
// { title: "Accounts", ele: this.accountsContent }, { title: "Preferences", ele: this.preferencesContent }];
const tabs = [{ title: "Accounts", ele: this.accountsContent }, { title: "Modes", ele: this.modesContent },
- { title: "Appearance", ele: this.appearanceContent }];
+ { title: "Appearance", ele: this.appearanceContent }, { title: "Text", ele: this.textContent }];
return <div className="settings-interface">
<div className="settings-panel">
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index dc5f488b2..6c4556250 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import Select from "react-select";
import * as RequestPromise from "request-promise";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc";
import { List } from "../../fields/List";
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util";
@@ -17,11 +17,13 @@ import { MainViewModal } from "../views/MainViewModal";
import { DocumentView } from "../views/nodes/DocumentView";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
import { SearchBox } from "../views/search/SearchBox";
+import { CurrentUserUtils } from "./CurrentUserUtils";
import { DocumentManager } from "./DocumentManager";
import { GroupManager, UserOptions } from "./GroupManager";
import { GroupMemberView } from "./GroupMemberView";
import { SelectionManager } from "./SelectionManager";
import "./SharingManager.scss";
+import { LinkManager } from "./LinkManager";
export interface User {
email: string;
@@ -38,7 +40,7 @@ interface GroupedOptions {
}
// const SharingKey = "sharingPermissions";
-// const PublicKey = "publicLinkPermissions";
+// const PublicKey = "all";
// const DefaultColor = "black";
// used to differentiate between individuals and groups when sharing
@@ -84,13 +86,14 @@ export class SharingManager extends React.Component<{}> {
private AclMap = new Map<symbol, string>([
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
- [AclAddonly, SharingPermissions.Add],
+ [AclAugment, SharingPermissions.Augment],
+ [AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
[AclAdmin, SharingPermissions.Admin]
]);
// private get linkVisible() {
- // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
+ // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
// }
public open = (target?: DocumentView, target_doc?: Doc) => {
@@ -100,7 +103,7 @@ export class SharingManager extends React.Component<{}> {
this.targetDoc = target_doc || target?.props.Document;
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
- this.permissions = SharingPermissions.Add;
+ this.permissions = SharingPermissions.Augment;
});
}
@@ -152,10 +155,11 @@ export class SharingManager extends React.Component<{}> {
}
});
return Promise.all(evaluating).then(() => {
- runInAction(() => {
+ runInAction(async () => {
for (const sharer of sharingDocs) {
if (!this.users.find(user => user.user.email === sharer.user.email)) {
this.users.push(sharer);
+ LinkManager.addLinkDB(sharer.linkDatabase);
}
}
});
@@ -172,10 +176,11 @@ export class SharingManager extends React.Component<{}> {
const target = targetDoc || this.targetDoc!;
const acl = `acl-${normalizeEmail(user.email)}`;
const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
+ const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
return !docs.map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc);
+ doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
if (permission === SharingPermissions.None) {
if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
@@ -184,8 +189,9 @@ export class SharingManager extends React.Component<{}> {
if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
}
- distributeAcls(acl, permission as SharingPermissions, doc);
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
+ this.setDashboardBackground(doc, permission as SharingPermissions);
if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
}).some(success => !success);
@@ -201,12 +207,13 @@ export class SharingManager extends React.Component<{}> {
const target = targetDoc || this.targetDoc!;
const key = normalizeEmail(StrCast(group.title));
const acl = `acl-${key}`;
+ const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
// ! ensures it returns true if document has been shared successfully, false otherwise
return !docs.map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc);
+ doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
if (permission === SharingPermissions.None) {
if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
@@ -215,7 +222,8 @@ export class SharingManager extends React.Component<{}> {
if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
}
- distributeAcls(acl, permission as SharingPermissions, doc);
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
+ this.setDashboardBackground(doc, permission as SharingPermissions);
if (group instanceof Doc) {
const members: string[] = JSON.parse(StrCast(group.members));
@@ -264,13 +272,34 @@ export class SharingManager extends React.Component<{}> {
});
}
else {
+ const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
docs.forEach(doc => {
- if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc);
+ const isDashboard = dashboards.indexOf(doc) !== -1;
+ if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
});
}
}
/**
+ * Sets the background of the Dashboard if it has been shared as a visual indicator
+ */
+ setDashboardBackground = async (doc: Doc, permission: SharingPermissions) => {
+ if (Doc.IndexOf(doc, DocListCast(CurrentUserUtils.MyDashboards.data)) !== -1) {
+ if (permission !== SharingPermissions.None) {
+ doc.isShared = true;
+ doc.backgroundColor = "green";
+ }
+ else {
+ const acls = doc[DataSym][AclSym];
+ if (Object.keys(acls).every(key => key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key]))) {
+ doc.isShared = undefined;
+ doc.backgroundColor = undefined;
+ }
+ }
+ }
+ }
+
+ /**
* Removes the documents shared with a user through a group when the user is removed from the group.
* @param group
* @param emailId
@@ -294,10 +323,11 @@ export class SharingManager extends React.Component<{}> {
*/
removeGroup = (group: Doc) => {
if (group.docsShared) {
+ const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
DocListCast(group.docsShared).forEach(doc => {
const acl = `acl-${StrCast(group.title)}`;
-
- distributeAcls(acl, SharingPermissions.None, doc);
+ const isDashboard = dashboards.indexOf(doc) !== -1;
+ distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined, isDashboard);
const members: string[] = JSON.parse(StrCast(group.members));
const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -310,11 +340,11 @@ export class SharingManager extends React.Component<{}> {
// private setExternalSharing = (permission: string) => {
- // const sharingDoc = this.sharingDoc;
- // if (!sharingDoc) {
+ // const targetDoc = this.targetDoc;
+ // if (!targetDoc) {
// return;
// }
- // sharingDoc[PublicKey] = permission;
+ // targetDoc["acl-" + PublicKey] = permission;
// }
// private get sharingUrl() {
@@ -339,10 +369,10 @@ export class SharingManager extends React.Component<{}> {
const dropdownValues: string[] = Object.values(SharingPermissions);
if (!uniform) dropdownValues.unshift("-multiple-");
if (override) dropdownValues.unshift("None");
- return dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission =>
+ return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
(
<option key={permission} value={permission}>
- {permission === SharingPermissions.Add ? "Can Augment" : permission}
+ {permission}
</option>
)
);
@@ -423,16 +453,16 @@ export class SharingManager extends React.Component<{}> {
}
}
- distributeOverCollection = (targetDoc?: Doc) => {
- const target = targetDoc || this.targetDoc!;
+ // distributeOverCollection = (targetDoc?: Doc) => {
+ // const target = targetDoc || this.targetDoc!;
- const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
- docs.forEach(doc => {
- for (const [key, value] of Object.entries(doc[AclSym])) {
- distributeAcls(key, this.AclMap.get(value)! as SharingPermissions, target);
- }
- });
- }
+ // const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
+ // docs.forEach(doc => {
+ // for (const [key, value] of Object.entries(doc[AclSym])) {
+ // distributeAcls(key, this.AclMap.get(value)! as SharingPermissions, target);
+ // }
+ // });
+ // }
/**
* Sorting algorithm to sort users.
@@ -519,7 +549,7 @@ export class SharingManager extends React.Component<{}> {
</select>
) : (
<div className={"permissions-dropdown"}>
- {permissions === SharingPermissions.Add ? "Can Augment" : permissions}
+ {permissions}
</div>
)}
</div>
@@ -565,7 +595,7 @@ export class SharingManager extends React.Component<{}> {
// the list of groups shared with
const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true);
- groupListMap.unshift({ title: "Public" });//, { title: "Override" });
+ groupListMap.unshift({ title: "Public" });//, { title: "ALL" });
const groupListContents = groupListMap.map(group => {
const groupKey = `acl-${StrCast(group.title)}`;
const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
@@ -614,6 +644,11 @@ export class SharingManager extends React.Component<{}> {
<div className={"close-button"} onClick={this.close}>
<FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
+ {/* {this.linkVisible ?
+ <div>
+ {this.sharingUrl}
+ </div> :
+ (null)} */}
{<div className="share-container">
<div className="share-setup">
<Select
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 33e624ef4..e4ac87aad 100644
--- a/src/client/views/.DS_Store
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index 2bac03af4..8a0e5480e 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -6,9 +6,9 @@
z-index: 10001;
height: $antimodemenu-height;
background: $dark-gray;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+ border-bottom: $standard-border;
+ // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
// border-radius: 0px 6px 6px 6px;
- z-index: 1001;
display: flex;
&.with-rows {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0b70ce68d..14d32ef12 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast, AclAdmin } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAugment, AclPrivate, AclEdit, AclSym, DocListCast, AclAdmin, AclSelfEdit } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { List } from '../../fields/List';
import { DateField } from '../../fields/DateField';
import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail } from '../../fields/util';
+import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail, inheritParentAcls } from '../../fields/util';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DocUtils } from '../documents/Documents';
import { returnFalse } from '../../Utils';
@@ -107,13 +107,6 @@ 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],
- [AclAdmin, SharingPermissions.Admin]
- ]);
lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
@@ -138,7 +131,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
const indocs = doc instanceof Doc ? [doc] : doc;
- const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
+ const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
if (docs.length) {
setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin
Doc.SetInPlace(doc, "isPushpin", undefined, true);
@@ -201,15 +194,14 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
added.forEach(d => {
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
- //else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true);
- // else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
+ if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
}
});
}
- if (effectiveAcl === AclAddonly) {
+ if (effectiveAcl === AclAugment) {
added.map(doc => {
+ if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
doc.context = this.props.Document;
if (annotationKey ?? this._annotationKey) Doc.GetProto(doc).annotationOn = this.props.Document;
this.props.layerProvider?.(doc, true);
@@ -223,9 +215,11 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
doc._stayInCollection = undefined;
doc.context = this.props.Document;
if (annotationKey ?? this._annotationKey) Doc.GetProto(doc).annotationOn = this.props.Document;
+
+ inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
});
const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
- if (annoDocs) annoDocs.push(...added);
+ if (annoDocs instanceof List) annoDocs.push(...added);
else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
targetDataDoc[(annotationKey ?? this.annotationKey) + "-lastModified"] = new DateField(new Date(Date.now()));
}
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 2a0b494f5..a112f4745 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -44,20 +44,21 @@ $linkGap : 3px;
}
.documentButtonBar {
- margin-top: $linkGap;
- grid-column: 1/4;
- width: max-content;
- height: auto;
display: flex;
flex-direction: row;
}
.documentButtonBar-button {
- pointer-events: auto;
- padding-right: 5px;
- width: 25px;
+ cursor: pointer;
+ display: flex;
+ width: 30px;
+ height: 30px;
+ align-content: center;
+ justify-content: center;
+ align-items: center;
}
+// depracated (now use .documentButtonBar-icon) for standard buttons
.documentButtonBar-linker {
height: 20px;
width: 20px;
@@ -73,6 +74,26 @@ $linkGap : 3px;
}
}
+.documentButtonBar-icon {
+ height: 80%;
+ width: 80%;
+ font-size: 100%;
+ text-align: center;
+ border-radius: 50%;
+ pointer-events: auto;
+ background-color: $dark-gray;
+ border: none;
+ transition: 0.2s ease all;
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ background-color: $black;
+ }
+}
+
.documentButtonBar-linker:hover {
cursor: pointer;
transform: scale(1.05);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a5d80cd22..5f09a322c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from '@material-ui/core';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
+import { Doc, DocCastAsync } from "../../fields/Doc";
import { RichTextField } from '../../fields/RichTextField';
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from "../../Utils";
@@ -24,7 +24,7 @@ import { DocumentView } from './nodes/DocumentView';
import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
import { TemplateMenu } from "./TemplateMenu";
import React = require("react");
-import { PresBox } from './nodes/PresBox';
+import { PresBox } from './nodes/trails/PresBox';
import { undoBatch } from '../util/UndoManager';
import { CollectionViewType } from './collections/CollectionView';
const higflyout = require("@hig/flyout");
@@ -110,7 +110,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
<div
- className="documentButtonBar-linker"
+ className="documentButtonBar-button"
style={{ animation }}
onClick={async () => {
await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
@@ -139,7 +139,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
title={<><div className="dash-tooltip">{title}</div></>}>
- <div className="documentButtonBar-linker"
+ <div className="documentButtonBar-button"
style={{ backgroundColor: this.pullColor }}
onPointerEnter={action(e => {
if (e.altKey) {
@@ -188,8 +188,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={
<div className="dash-tooltip">{"follow primary link on click"}</div>}>
- <div className="documentButtonBar-linker"
- style={{ color: targetDoc.isLinkButton ? "black" : "white", backgroundColor: targetDoc.isLinkButton ? "white" : "black" }}
+ <div className="documentButtonBar-icon"
+ style={{ color: targetDoc.isLinkButton ? "black" : "white" }}
onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
</div>
@@ -200,7 +200,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={
<div className="dash-tooltip">{SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}</div>}>
- <div className="documentButtonBar-linker"
+ <div className="documentButtonBar-icon"
style={{ color: "white" }}
onClick={undoBatch(e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, { setPosition: e.shiftKey ? true : undefined })))}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
@@ -243,7 +243,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 17, transform: 'translate(0, 1px)' }} />;
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with current view"}</div></>}>
- <div className="documentButtonBar-linker" onClick={() => this.pinWithView(targetDoc)}>
+ <div className="documentButtonBar-icon" onClick={() => this.pinWithView(targetDoc)}>
{presPinWithViewIcon}
</div>
</Tooltip>;
@@ -253,8 +253,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get shareButton() {
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Sharing Manager"}</div></>}>
- <div className="documentButtonBar-linker" style={{ color: "white" }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="users" />
+ <div className="documentButtonBar-icon" style={{ color: "white" }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="users" />
</div></Tooltip >;
}
@@ -262,8 +262,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get menuButton() {
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`Open Context Menu`}</div></>}>
- <div className="documentButtonBar-linker" style={{ color: "white", cursor: "pointer" }} onClick={e => this.openContextMenu(e)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="bars" />
+ <div className="documentButtonBar-icon" style={{ color: "white", cursor: "pointer" }} onClick={e => this.openContextMenu(e)}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
</div></Tooltip >;
}
@@ -271,9 +271,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get moreButton() {
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${CurrentUserUtils.propertiesWidth > 0 ? "Close" : "Open"} Properties Panel`}</div></>}>
- <div className="documentButtonBar-linker" style={{ color: "white", cursor: "e-resize" }} onClick={action(e =>
+ <div className="documentButtonBar-icon" style={{ color: "white", cursor: "e-resize" }} onClick={action(e =>
CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="ellipsis-h"
+ <FontAwesomeIcon className="documentdecorations-icon" icon="ellipsis-h"
/>
</div></Tooltip >;
}
@@ -286,7 +286,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<Flyout anchorPoint={anchorPoints.LEFT_TOP}
content={<MetadataEntryMenu docs={this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
<div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />}
</div>
</Flyout>
</div></Tooltip>;
@@ -348,16 +348,17 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
if (!this.view0) return (null);
const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField;
+ const doc = this.view0?.props.Document;
const considerPull = isText && this.considerGoogleDocsPull;
const considerPush = isText && this.considerGoogleDocsPush;
return <div className="documentButtonBar">
<div className="documentButtonBar-button">
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
</div>
- {DocumentLinksButton.StartLink || !Doc.UserDoc()["documentLinksButton-fullMenu"] ? <div className="documentButtonBar-button">
+ {(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
</div> : (null)}
- {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
+ {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
{this.templateButton}
</div>
/*<div className="documentButtonBar-button">
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 1715f35e7..316f63240 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -263,7 +263,6 @@ $linkGap : 3px;
}
.link-button-container {
- padding: $linkGap;
border-radius: 10px;
width: max-content;
height: auto;
@@ -271,6 +270,9 @@ $linkGap : 3px;
flex-direction: row;
z-index: 998;
position: absolute;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
background: $medium-gray;
}
@@ -334,7 +336,7 @@ $linkGap : 3px;
}
.documentdecorations-icon {
- margin-top: 3px;
+ margin: 0px;
}
.templating-button,
.docDecs-tagButton {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d24ab974c..118d2e7c7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -27,6 +27,7 @@ import { LightboxView } from './LightboxView';
import { DocumentView } from "./nodes/DocumentView";
import React = require("react");
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { DateField } from '../../fields/DateField';
@observer
export class DocumentDecorations extends React.Component<{ boundsLeft: number, boundsTop: number }, { value: string }> {
@@ -367,6 +368,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
dW && (doc._width = actualdW);
dH && (doc._autoHeight = false);
}
+ doc._lastModified = new DateField();
}
const val = this._dragHeights.get(docView.layoutDoc);
if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index 5dc0c1962..1aebedf2e 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -26,4 +26,10 @@
width: 100%;
background: inherit;
pointer-events: all;
-} \ No newline at end of file
+}
+
+.editableView-input:focus {
+ border: none;
+ outline: none;
+}
+ \ No newline at end of file
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 23f22c774..6213a4075 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -6,8 +6,13 @@ import { setupMoveUpEvents, emptyFunction } from "../../Utils";
import { UndoManager } from "../util/UndoManager";
import { ControlPoint, InkData, PointData } from "../../fields/InkField";
import { Transform } from "../util/Transform";
+import { Colors } from "./global/globalEnums";
+import { Doc } from "../../fields/Doc";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
export interface InkControlProps {
+ inkDoc: Doc;
data: InkData;
addedPoints: PointData[];
format: number[];
@@ -30,19 +35,20 @@ export class InkControls extends React.Component<InkControlProps> {
const controlUndo = UndoManager.StartBatch("DocDecs set radius");
const screenScale = this.props.ScreenToLocalTransform().Scale;
const order = controlIndex % 4;
- const handleIndexA = order === 2 ? controlIndex - 1 : controlIndex - 2;
+ const handleIndexA = order === 2 ? controlIndex - 1 : controlIndex - 2;
const handleIndexB = order === 2 ? controlIndex + 2 : controlIndex + 1;
+ const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
setupMoveUpEvents(this, e,
(e: PointerEvent, down: number[], delta: number[]) => {
InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
return false;
},
- () => controlUndo?.end(),
- emptyFunction);
- // action((e: PointerEvent, doubleTap: boolean | undefined) =>
- // { if (doubleTap && InkStrokeProperties.Instance?._brokenIndices.includes(controlIndex)) {
- // InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
- // }}));
+ () => controlUndo?.end(),
+ action((e: PointerEvent, doubleTap: boolean | undefined) => {
+ if (doubleTap && brokenIndices && brokenIndices.includes(controlIndex)) {
+ InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+ }
+ }));
}
}
@@ -75,7 +81,7 @@ export class InkControls extends React.Component<InkControlProps> {
@action onLeaveControl = () => { this._overControl = -1; };
@action onEnterAddPoint = (i: number) => { this._overAddPoint = i; };
@action onLeaveAddPoint = () => { this._overAddPoint = -1; };
-
+
render() {
const formatInstance = InkStrokeProperties.Instance;
if (!formatInstance) return (null);
@@ -90,41 +96,42 @@ export class InkControls extends React.Component<InkControlProps> {
}
}
const addedPoints = this.props.addedPoints;
- const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;
+ const [left, top, scaleX, scaleY, strokeWidth] = this.props.format;
return (
<>
{addedPoints.map((pts, i) =>
<svg height="10" width="10" key={`add${i}`}>
- <circle
- cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
- cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
- r={strokeWidth / 2}
- stroke={this._overAddPoint === i ? "#1F85DE" : "transparent"}
- strokeWidth={dotsize / 4} fill={this._overAddPoint === i ? "#1F85DE" : "transparent"}
- onPointerDown={() => { formatInstance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }}
- onMouseEnter={() => this.onEnterAddPoint(i)}
- onMouseLeave={this.onLeaveAddPoint}
- pointerEvents="all"
+ <circle
+ cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
+ cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+ r={strokeWidth / 1.5}
+ stroke={this._overAddPoint === i ? Colors.MEDIUM_BLUE : "transparent"}
+ strokeWidth={0} fill={this._overAddPoint === i ? Colors.MEDIUM_BLUE : "transparent"}
+ onPointerDown={() => { formatInstance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }}
+ onMouseEnter={() => this.onEnterAddPoint(i)}
+ onMouseLeave={this.onLeaveAddPoint}
+ pointerEvents="all"
cursor="all-scroll"
/>
</svg>
)}
{controlPoints.map((control, i) =>
<svg height="10" width="10" key={`ctrl${i}`}>
- <rect
- x={(control.X - left - strokeWidth / 2) * scaleX}
- y={(control.Y - top - strokeWidth / 2) * scaleY}
- height={this._overControl === i ? strokeWidth * 1.5 : strokeWidth}
- width={this._overControl === i ? strokeWidth * 1.5 : strokeWidth}
- strokeWidth={strokeWidth / 6} stroke="#1F85DE"
- fill={formatInstance?._currentPoint === control.I ? "#1F85DE" : "white"}
- onPointerDown={(e) => {
- this.changeCurrPoint(control.I);
- this.onControlDown(e, control.I); }}
- onMouseEnter={() => this.onEnterControl(i)}
- onMouseLeave={this.onLeaveControl}
- pointerEvents="all"
+ <rect
+ x={(control.X - left - strokeWidth / 2) * scaleX}
+ y={(control.Y - top - strokeWidth / 2) * scaleY}
+ height={this._overControl === i ? strokeWidth * 1.5 : strokeWidth}
+ width={this._overControl === i ? strokeWidth * 1.5 : strokeWidth}
+ strokeWidth={strokeWidth / 6} stroke={Colors.MEDIUM_BLUE}
+ fill={formatInstance?._currentPoint === control.I ? Colors.MEDIUM_BLUE : Colors.WHITE}
+ onPointerDown={(e) => {
+ this.changeCurrPoint(control.I);
+ this.onControlDown(e, control.I);
+ }}
+ onMouseEnter={() => this.onEnterControl(i)}
+ onMouseLeave={this.onLeaveControl}
+ pointerEvents="all"
cursor="default"
/>
</svg>
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index 28b6dd820..f1eb4b9db 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -10,6 +10,7 @@ import { Doc } from "../../fields/Doc";
import { listSpec } from "../../fields/Schema";
import { List } from "../../fields/List";
import { Cast } from "../../fields/Types";
+import { Colors } from "./global/globalEnums";
export interface InkHandlesProps {
inkDoc: Doc;
@@ -50,6 +51,7 @@ export class InkHandles extends React.Component<InkHandlesProps> {
onBreakTangent = (e: KeyboardEvent, controlIndex: number) => {
const doc: Doc = this.props.inkDoc;
if (["Alt"].includes(e.key)) {
+ e.stopPropagation();
if (doc) {
const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
if (brokenIndices && !brokenIndices.includes(controlIndex)) {
@@ -80,7 +82,7 @@ export class InkHandles extends React.Component<InkHandlesProps> {
handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
}
}
- const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;
+ const [left, top, scaleX, scaleY, strokeWidth] = this.props.format;
return (
<>
@@ -91,7 +93,7 @@ export class InkHandles extends React.Component<InkHandlesProps> {
cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
r={strokeWidth / 2}
strokeWidth={0}
- fill="#1F85DE"
+ fill={Colors.MEDIUM_BLUE}
onPointerDown={(e) => this.onHandleDown(e, pts.I)}
pointerEvents="all"
cursor="default"
@@ -104,16 +106,16 @@ export class InkHandles extends React.Component<InkHandlesProps> {
y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
- stroke="#1F85DE"
- strokeWidth={dotsize / 8}
+ stroke={Colors.MEDIUM_BLUE}
+ strokeWidth={strokeWidth / 4}
display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
<line
x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
- stroke="#1F85DE"
- strokeWidth={dotsize / 8}
+ stroke={Colors.MEDIUM_BLUE}
+ strokeWidth={strokeWidth / 4}
display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
</svg>)}
</>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 1a3585f3e..6444e4451 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -35,7 +35,7 @@ export class InkStrokeProperties {
* @param func The inputted function.
* @param requireCurrPoint Indicates whether the current selected point is needed.
*/
- applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
+ applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
var appliedFunc = false;
this.selectedInk?.forEach(action(inkView => {
if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) {
@@ -91,16 +91,16 @@ export class InkStrokeProperties {
});
}
}
- if (end === 0) end = points.length-1;
+ if (end === 0) end = points.length - 1;
// Index of new control point with regards to the ink data.
const newIndex = Math.floor(counter / 2) * 4 + 2;
// Creating new ink data with the new control point and handle points inputted.
for (let i = 0; i < ink.length; i++) {
- if (i === newIndex) {
- const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index+1), points.slice(index, end), newControl);
+ if (i === newIndex) {
+ const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl);
newPoints.push(handleA, newControl, newControl, handleB);
// Adjusting the magnitude of the left handle line of the right neighboring control point.
- const [rightControl, rightHandle] = [points[end], ink[i]];
+ const [rightControl, rightHandle] = [points[end], ink[i]];
const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle);
rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y });
} else if (i === newIndex - 1) {
@@ -111,14 +111,14 @@ export class InkStrokeProperties {
} else {
ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
}
-
+
}
let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
// Updating the indices of the control points whose handle tangency has been broken.
if (brokenIndices) {
- brokenIndices = new List(brokenIndices.map((control) => {
+ brokenIndices = new List(brokenIndices.map((control) => {
if (control >= newIndex) {
- return control + 4;
+ return control + 4;
} else {
return control;
}
@@ -158,7 +158,7 @@ export class InkStrokeProperties {
getNewHandlePoints = (C: PointData[], D: PointData[], newControl: PointData) => {
const [m, n] = [C.length, D.length];
let handleSizeA = Math.sqrt((Math.pow(newControl.X - C[0].X, 2)) + (Math.pow(newControl.Y - C[0].Y, 2)));
- let handleSizeB = Math.sqrt((Math.pow(D[n-1].X - newControl.X, 2)) + (Math.pow(D[n-1].Y - newControl.Y, 2)));
+ let handleSizeB = Math.sqrt((Math.pow(D[n - 1].X - newControl.X, 2)) + (Math.pow(D[n - 1].Y - newControl.Y, 2)));
// Scaling adjustments to improve the ratio between the magnitudes of the two handle lines.
// (Ensures that the new point added doesn't augment the inital shape of the curve much).
if (handleSizeA < 75 && handleSizeB < 75) {
@@ -173,7 +173,7 @@ export class InkStrokeProperties {
handleSizeB *= 2;
}
// Finding the last leg of the derivative curve of C.
- const dC = { X: (handleSizeA / n) * (C[m-1].X - C[m-2].X), Y: (handleSizeA / n) * (C[m-1].Y - C[m-2].Y) };
+ const dC = { X: (handleSizeA / n) * (C[m - 1].X - C[m - 2].X), Y: (handleSizeA / n) * (C[m - 1].Y - C[m - 2].Y) };
// Finding the first leg of the derivative curve of D.
const dD = { X: (handleSizeB / m) * (D[1].X - D[0].X), Y: (handleSizeB / m) * (D[1].Y - D[0].Y) };
const handleA = { X: newControl.X - dC.X, Y: newControl.Y - dC.Y };
@@ -257,15 +257,24 @@ export class InkStrokeProperties {
return newPoints;
})
+ /**
+ * Snaps a control point with broken tangency back to synced rotation.
+ * @param handleIndexA The handle point that retains its current position.
+ * @param handleIndexB The handle point that is rotated to be 180 degrees from its opposite.
+ */
snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => {
this.applyFunction((doc: Doc, ink: InkData) => {
- // doc.brokenIndices.splice(this._brokenIndices.indexOf(controlIndex), 1);
- const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
- const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
- const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
- const newHandleB = this.rotatePoint(handleB, controlPoint, angleDifference);
- ink[handleIndexB] = newHandleB;
- return ink;
+ const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
+ if (brokenIndices) {
+ brokenIndices.splice(brokenIndices.indexOf(controlIndex), 1);
+ doc.brokenInkIndices = brokenIndices;
+ const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
+ const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
+ const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
+ const newHandleB = this.rotatePoint(handleB, controlPoint, angleDifference);
+ ink[handleIndexB] = newHandleB;
+ return ink;
+ }
});
}
@@ -274,13 +283,12 @@ export class InkStrokeProperties {
*/
@action
rotatePoint = (target: PointData, origin: PointData, angle: number) => {
- target.X -= origin.X;
- target.Y -= origin.Y;
- const newX = Math.cos(angle) * target.X - Math.sin(angle) * target.Y;
- const newY = Math.sin(angle) * target.X + Math.cos(angle) * target.Y;
- target.X = newX + origin.X;
- target.Y = newY + origin.Y;
- return target;
+ const rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y };
+ const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y;
+ const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y;
+ rotatedTarget.X = newX + origin.X;
+ rotatedTarget.Y = newY + origin.Y;
+ return rotatedTarget;
}
/**
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index bd71aaf19..63cefbf67 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -19,6 +19,7 @@ import { InkStrokeProperties } from "./InkStrokeProperties";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InkControls } from "./InkControls";
import { InkHandles } from "./InkHandles";
+import { Colors } from "./global/globalEnums";
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@@ -43,14 +44,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
}
- @action
- public static toggleMask = (inkDoc: Doc) => {
+ public static toggleMask = action((inkDoc: Doc) => {
inkDoc.isInkMask = !inkDoc.isInkMask;
inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
inkDoc.color = "#9b9b9bff";
inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
- }
+ });
/**
* Handles the movement of the entire ink object when the user clicks and drags.
@@ -64,8 +64,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
}
}
+ /**
+ * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
+ */
+ @action
+ toggleControlButton = () => {
+ if (!this.props.isSelected() && this._properties) {
+ this._properties._controlButton = false;
+ }
+ }
+
render() {
TraceMobx();
+ this.toggleControlButton();
// Extracting the ink data and formatting information of the current ink stroke.
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
const inkDoc: Doc = this.layoutDoc;
@@ -86,32 +97,17 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
// Visually renders the polygonal line made by the user.
- const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
- StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
- StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
- this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1,
- false);
+ const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
+ StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
// Thin blue line indicating that the current ink stroke is selected.
- const selectedLine = InteractionUtils.CreatePolyline(
- data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6,
- strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
- StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
- StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
- this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1,
- false);
+ const selectedLine = InteractionUtils.CreatePolyline(data, left - strokeWidth / 3, top - strokeWidth / 3, Colors.MEDIUM_BLUE, strokeWidth / 6, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+ StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = InteractionUtils.CreatePolyline(data, left, top,
- this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth,
- strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
- StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "",
- this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
+ const clickableLine = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
+ StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
// Set of points rendered upon the ink that can be added if a user clicks on one.
- const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
- StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
- StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
- this.props.isSelected() && strokeWidth <= 5, false);
+ const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
+ StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
return (
<svg className="inkStroke"
@@ -138,14 +134,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
{this.props.isSelected() && this._properties?._controlButton ?
<>
<InkControls
+ inkDoc={inkDoc}
data={data}
addedPoints={addedPoints}
- format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+ format={[left, top, scaleX, scaleY, strokeWidth]}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
<InkHandles
inkDoc={inkDoc}
data={data}
- format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+ format={[left, top, scaleX, scaleY, strokeWidth]}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
</> : ""}
</svg>
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 60327f1bf..7553c8118 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -12,6 +12,7 @@ import { LinkManager } from "../util/LinkManager";
AssignAllExtensions();
(async () => {
+ MainView.Live = window.location.search.includes("live");
window.location.search.includes("safe") && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
if (info.id !== "__guest__") {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 08bcd55ae..53e872a66 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -22,10 +22,6 @@
height: 100%;
}
-.mainContent-div-flyout {
- left: calc(-1 * var(--flyoutHandleWidth));
-}
-
// add nodes menu. Note that the + button is actually an input label, not an actual button.
.mainView-docButtons {
position: absolute;
@@ -115,6 +111,14 @@
user-select: none;
}
+.properties-container {
+ height: 100%;
+ position: relative;
+ left: 100%;
+ top: calc(-100% - 36px);
+ z-index: 3000;
+}
+
.mainView-propertiesDragger {
//background-color: rgb(140, 139, 139);
background-color: $light-gray;
@@ -122,7 +126,6 @@
width: 17px;
position: absolute;
top: 50%;
- border: 1px black solid;
border-radius: 0;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
@@ -145,18 +148,6 @@
}
}
-.mainiView-propertiesView {
- display: flex;
- flex-direction: column;
- height: 100%;
- position: absolute;
- right: 0;
- top: 0;
- border-left: solid 1px;
- z-index: 100000;
- cursor: auto;
-}
-
.mainView-innerContent,
.mainView-innerContent-dark {
display: contents;
@@ -176,10 +167,10 @@
}
.propertiesView {
- right: 0;
+ left: 0;
position: absolute;
z-index: 2;
- background-color: $medium-gray;
+ background-color: $light-gray;
.editable-title {
background-color: $light-gray;
@@ -233,6 +224,7 @@
.mainView-menuPanel {
min-width: var(--menuPanelWidth);
background-color: $dark-gray;
+ border-right: $standard-border;
.collectionStackingView {
scrollbar-width: none;
@@ -432,34 +424,4 @@
display: block;
width: 500px;
height: 1000px;
-}
-
-.lm_drag_tab {
- padding: 0;
- width: 15px !important;
- height: 15px !important;
- position: relative !important;
- display: inline-flex !important;
- align-items: center;
- top: 0 !important;
- right: unset !important;
- left: 0 !important;
-}
-
-.lm_close_tab {
- padding: 0;
- width: 15px !important;
- height: 15px !important;
- position: relative !important;
- display: inline-flex !important;
- align-items: center;
- top: 0 !important;
- right: unset !important;
- left: 0 !important;
-}
-
-.lm_tab,
-.lm_tab_active {
- display: flex !important;
- padding-right: 0 !important;
} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f34851b00..b0b8d7f41 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -63,11 +63,14 @@ import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { SearchBox } from './search/SearchBox';
import { DefaultStyleProvider, DashboardStyleProvider, StyleProp } from './StyleProvider';
+import { TopBar } from './topbar/TopBar';
+import { Colors } from './global/globalEnums';
const _global = (window /* browser */ || global /* node */) as any;
@observer
export class MainView extends React.Component {
public static Instance: MainView;
+ public static Live: boolean = false;
private _docBtnRef = React.createRef<HTMLDivElement>();
@observable public LastButton: Opt<Doc>;
@observable private _windowWidth: number = 0;
@@ -78,7 +81,7 @@ export class MainView extends React.Component {
@observable private _sidebarContent: any = this.userDoc?.sidebar;
@observable private _flyoutWidth: number = 0;
- @computed private get topOffset() { return (CollectionMenu.Instance?.Pinned ? 35 : 0) + Number(SEARCH_PANEL_HEIGHT.replace("px", "")); }
+ @computed private get topOffset() { return Number(SEARCH_PANEL_HEIGHT.replace("px", "")); } //TODO remove
@computed private get leftOffset() { return this.menuPanelWidth() - 2; }
@computed private get userDoc() { return Doc.UserDoc(); }
@computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
@@ -103,8 +106,11 @@ export class MainView extends React.Component {
}
new InkStrokeProperties();
this._sidebarContent.proto = undefined;
- DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeHidden"]); // can play with these fields on someone else's
-
+ if (!MainView.Live) {
+ DocServer.setPlaygroundFields(["dataTransition", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
+ "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap",
+ "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
+ }
DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
const tag = document.createElement('script');
@@ -178,12 +184,12 @@ export class MainView extends React.Component {
const targets = document.elementsFromPoint(e.x, e.y);
if (targets.length) {
const targClass = targets[0].className.toString();
- if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
- const check = targets.some((thing) =>
- (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
- thing.className === "collectionSchema-header-menuOptions"));
- !check && SearchBox.Instance.resetSearch(true);
- }
+ // if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
+ // const check = targets.some((thing) =>
+ // (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
+ // thing.className === "collectionSchema-header-menuOptions"));
+ // !check && SearchBox.Instance.resetSearch(true);
+ // }
!targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
!["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
}
@@ -192,7 +198,7 @@ export class MainView extends React.Component {
initEventListeners = () => {
window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
window.addEventListener("dragover", e => e.preventDefault(), false);
- document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
+ // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
document.addEventListener("pointerdown", this.globalPointerDown);
document.addEventListener("click", (e: MouseEvent) => {
if (!e.cancelBubble) {
@@ -242,8 +248,9 @@ export class MainView extends React.Component {
}
getPWidth = () => this._panelWidth - this.propertiesWidth();
- getPHeight = () => this._panelHeight;
+ getPHeight = () => this._panelHeight - (CollectionMenu.Instance?.Pinned ? 35 : 0);
getContentsHeight = () => this._panelHeight;
+ getMenuPanelHeight = () => this._panelHeight + (CollectionMenu.Instance?.Pinned ? 35 : 0);
@computed get mainDocView() {
return <DocumentView key="main"
@@ -275,10 +282,12 @@ export class MainView extends React.Component {
@computed get dockingContent() {
return <div key="docking" className={`mainContent-div${this._flyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
+ // style={{ minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`, width: `calc(100% - ${this._flyoutWidth + this.propertiesWidth()}px)` }}>
+ // FIXME update with property panel width
style={{
minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`,
transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
- width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`
+ //TODO:glr width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`
}}>
{!this.mainContainer ? (null) : this.mainDocView}
</div>;
@@ -358,7 +367,7 @@ export class MainView extends React.Component {
removeDocument={returnFalse}
ScreenToLocalTransform={this.sidebarScreenToLocal}
PanelWidth={this.menuPanelWidth}
- PanelHeight={this.getContentsHeight}
+ PanelHeight={this.getMenuPanelHeight}
renderDepth={0}
docViewPath={returnEmptyDoclist}
focus={DocUtils.DefaultFocus}
@@ -401,20 +410,27 @@ export class MainView extends React.Component {
}
@computed get mainInnerContent() {
+ const width = this.propertiesWidth() + this._flyoutWidth + this.menuPanelWidth();
+ const transform = this._flyoutWidth ? 'translate(-28px, 0px)' : undefined;
return <>
{this.menuPanel}
<div key="inner" className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
{this.flyout}
- <div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined, }} onPointerDown={this.onFlyoutPointerDown} >
+ <div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
<FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
</div>
+ <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
+ <CollectionMenu />
- {this.dockingContent}
+ {this.dockingContent}
- <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
- <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? "white" : "black"} size="sm" />
+ <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._flyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
+ <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? Colors.WHITE : Colors.BLACK} size="sm" />
+ </div>
+ <div className="properties-container">
+ {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.getContentsHeight()} />}
+ </div>
</div>
- {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.getContentsHeight()} />}
</div>
</>;
}
@@ -525,35 +541,8 @@ export class MainView extends React.Component {
@computed get search() {
TraceMobx();
- return <div className="mainView-searchPanel">
- <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
- DataDoc={CurrentUserUtils.MySearchPanelDoc}
- fieldKey="data"
- dropAction="move"
- isSelected={returnTrue}
- isContentActive={returnTrue}
- select={returnTrue}
- setHeight={returnFalse}
- addDocument={undefined}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- rootSelected={returnTrue}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={this.getPWidth}
- PanelHeight={this.getPHeight}
- renderDepth={0}
- focus={DocUtils.DefaultFocus}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
+ return <div className="mainView-topbar">
+ <TopBar />
</div>;
}
@@ -605,7 +594,6 @@ export class MainView extends React.Component {
<GoogleAuthenticationManager />
<DocumentDecorations boundsLeft={this.leftOffset} boundsTop={this.topOffset} />
{this.search}
- <CollectionMenu />
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 717bd0768..a3a3bce56 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,6 +1,6 @@
import { action, observable, ObservableMap, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { List } from "../../fields/List";
import { NumCast } from "../../fields/Types";
@@ -120,9 +120,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
return marqueeAnno;
}
- const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 });
+ const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: "transparent", title: "Selection on " + this.props.rootDoc.title });
+ let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
+ let maxY = -Number.MIN_VALUE;
const annoDocs: Doc[] = [];
savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
const textRegion = new Doc();
@@ -135,12 +137,16 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
annoDocs.push(textRegion);
anno.remove();
minY = Math.min(NumCast(textRegion.y), minY);
+ minX = Math.min(NumCast(textRegion.x), minX);
+ maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
}));
const textRegionAnnoProto = Doc.GetProto(textRegionAnno);
textRegionAnnoProto.y = Math.max(minY, 0);
- textRegionAnnoProto.x = Math.max(maxX, 0);
+ textRegionAnnoProto.x = Math.max(minX, 0);
+ textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
+ textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
// mainAnnoDocProto.text = this._selectionText;
textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs);
savedAnnoMap.clear();
@@ -150,7 +156,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => {
// creates annotation documents for current highlights
const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
- const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
+ const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
!savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
return annotationDoc as Doc ?? undefined;
}
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index fa45a065d..321b83f52 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -1,6 +1,8 @@
+@import "./global/globalCssVariables.scss";
+
.propertiesView {
height: 100%;
- font-family: "Noto Sans";
+ font-family: "Roboto";
cursor: auto;
overflow-x: hidden;
@@ -28,9 +30,7 @@
color: grey;
cursor: pointer;
}
-
}
-
}
.propertiesView-name {
@@ -80,7 +80,6 @@
padding-bottom: 10px;
padding-top: 8px;
}
-
}
.propertiesView-sharing {
@@ -140,8 +139,6 @@
}
}
-
-
.change-buttons {
display: flex;
@@ -216,7 +213,6 @@
}
}
-
.propertiesView-appearance {
//border-bottom: 1px solid black;
//padding: 8.5px;
@@ -305,7 +301,7 @@
.notify-button-icon {
width: 6px;
height: 6.5px;
- margin-left: .5px;
+ margin-left: 0.5px;
}
&:hover {
@@ -331,7 +327,6 @@
}
.propertiesView-sharingTable {
-
// whatever's commented out - add it back in when adding the buttons
// border: 1.5px solid black;
@@ -347,7 +342,6 @@
width: 92%;
.propertiesView-sharingTable-item {
-
display: flex;
// padding: 5px;
padding: 3px;
@@ -421,7 +415,6 @@
cursor: pointer;
}
}
-
}
.propertiesView-fields-checkbox {
@@ -468,7 +461,6 @@
}
.propertiesView-contexts {
-
.propertiesView-contexts-title {
font-weight: bold;
font-size: 12.5px;
@@ -499,11 +491,9 @@
overflow: hidden;
padding: 10px;
}
-
}
.propertiesView-layout {
-
.propertiesView-layout-title {
font-weight: bold;
font-size: 12.5px;
@@ -534,7 +524,6 @@
overflow: hidden;
padding: 10px;
}
-
}
.propertiesView-presTrails {
@@ -576,7 +565,6 @@
}
.inking-button {
-
display: flex;
.inking-button-points {
@@ -635,7 +623,6 @@
}
.inputBox {
-
margin-top: 10px;
display: flex;
height: 19px;
@@ -658,7 +645,6 @@
}
.inputBox-button {
-
.inputBox-button-up {
background-color: #333333;
height: 9px;
@@ -690,7 +676,6 @@
cursor: pointer;
}
}
-
}
}
@@ -767,7 +752,6 @@
}
.widthAndDash {
-
.width {
.width-top {
display: flex;
@@ -792,13 +776,11 @@
}
.arrows {
-
display: flex;
margin-bottom: 3px;
margin-left: 4px;
.arrows-head {
-
display: flex;
margin-right: 35px;
@@ -827,7 +809,6 @@
}
.dashed {
-
display: flex;
margin-left: 64px;
margin-bottom: 6px;
@@ -844,19 +825,15 @@
}
.editable-title {
- border: none;
padding: 6px;
padding-bottom: 2px;
- background: #eeeeee;
- border-top: 1px solid;
- border-left: 1px solid;
+ border: solid 1px $dark-gray;
&:hover {
- border: 0.75px solid rgb(122, 28, 28);
+ border: 0.75px solid $medium-blue;
}
}
-
.properties-flyout {
grid-column: 2/4;
-} \ No newline at end of file
+}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 4df3e4f00..de437e1df 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,7 +5,7 @@ import { intersection } from "lodash";
import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym, AclSelfEdit } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { InkField } from "../../fields/InkField";
import { ComputedField } from "../../fields/ScriptField";
@@ -24,7 +24,7 @@ import { EditableView } from "./EditableView";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { DocumentView, StyleProviderFunc } from "./nodes/DocumentView";
import { KeyValueBox } from "./nodes/KeyValueBox";
-import { PresBox } from "./nodes/PresBox";
+import { PresBox } from "./nodes/trails/PresBox";
import { PropertiesButtons } from "./PropertiesButtons";
import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
import "./PropertiesView.scss";
@@ -342,12 +342,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return <select className="permissions-select"
value={permission}
onChange={e => this.changePermissions(e, user)}>
- {dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission => {
- return (
- <option key={permission} value={permission}>
- {permission === SharingPermissions.Add ? "Can Augment" : permission}
- </option>);
- })}
+ {dropdownValues.filter(permission =>
+ !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
+ <option key={permission} value={permission}> {permission} </option>)}
</select>;
}
@@ -402,7 +399,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
[AclUnset, "None"],
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
- [AclAddonly, SharingPermissions.Add],
+ [AclAugment, SharingPermissions.Augment],
+ [AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
[AclAdmin, SharingPermissions.Admin]
]);
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 9c5a54574..1f9763d18 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -23,6 +23,8 @@ interface ExtraProps {
layoutDoc: Doc;
rootDoc: Doc;
dataDoc: Doc;
+ showSidebar: boolean;
+ nativeWidth: number;
whenChildContentsActiveChanged: (isActive: boolean) => void;
ScreenToLocalTransform: () => Transform;
sidebarAddDocument: (doc: (Doc | Doc[]), suffix: string) => boolean;
@@ -31,18 +33,22 @@ interface ExtraProps {
}
@observer
export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
+ constructor(props: Readonly<FieldViewProps & ExtraProps>) {
+ super(props);
+ // this.props.dataDoc[this.sidebarKey] = new List<Doc>(); // bcz: can't do this here. it blows away existing things and isn't a robust solution for making sure the field exists -- instead this should happen when the document is created and/or shared
+ }
_stackRef = React.createRef<CollectionStackingView>();
@computed get allHashtags() {
const keys = new Set<string>();
- DocListCast(this.props.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key)));
+ DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key)));
return Array.from(keys.keys()).filter(key => key[0]).filter(key => !key.startsWith("_") && (key[0] === "#" || key[0] === key[0].toUpperCase())).sort();
}
@computed get allUsers() {
const keys = new Set<string>();
- DocListCast(this.props.rootDoc[this.sidebarKey()]).forEach(doc => keys.add(StrCast(doc.author)));
+ DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
return Array.from(keys.keys()).sort();
}
- get filtersKey() { return "_" + this.sidebarKey() + "-docFilters"; }
+ get filtersKey() { return "_" + this.sidebarKey + "-docFilters"; }
anchorMenuClick = (anchor: Doc) => {
const startup = StrListCast(this.props.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" ");
@@ -59,7 +65,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
this._stackRef.current?.focusDocument(target);
}
makeDocUnfiltered = (doc: Doc) => {
- if (DocListCast(this.props.rootDoc[this.sidebarKey()]).includes(doc)) {
+ if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) {
if (this.props.layoutDoc[this.filtersKey]) {
this.props.layoutDoc[this.filtersKey] = new List<string>();
return true;
@@ -67,19 +73,20 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
}
return false;
}
- sidebarKey = () => this.props.fieldKey + "-sidebar";
+
+ get sidebarKey() { return this.props.fieldKey + "-sidebar"; }
filtersHeight = () => 38;
screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1);
- panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF ? this.props.PanelWidth() : (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
+ panelWidth = () => !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF ? this.props.PanelWidth() : (NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.nativeWidth);
panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
- addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey());
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey());
- removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey());
+ addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
+ removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
if (property === StyleProp.ShowTitle) {
- return doc === this.props.rootDoc ? 0 : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
+ return doc === this.props.rootDoc ? undefined : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
}
return this.props.styleProvider?.(doc, props, property);
}
@@ -87,18 +94,18 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
const renderTag = (tag: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
return <div key={tag} className={`sidebarAnnos-filterTag${active ? "-active" : ""}`}
- onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}>
+ onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, "check", true, this.sidebarKey, e.shiftKey)}>
{tag}
</div>;
};
const renderUsers = (user: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`author:${user}:check`);
return <div key={user} className={`sidebarAnnos-filterUser${active ? "-active" : ""}`}
- onClick={e => Doc.setDocFilter(this.props.rootDoc, "author", user, "check", true, this.sidebarKey(), e.shiftKey)}>
+ onClick={e => Doc.setDocFilter(this.props.rootDoc, "author", user, "check", true, this.sidebarKey, e.shiftKey)}>
{user}
</div>;
};
- return !this.props.layoutDoc._showSidebar ? (null) :
+ return !this.props.showSidebar ? (null) :
<div style={{
position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: 0, right: 0,
background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
@@ -118,7 +125,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
PanelWidth={this.panelWidth}
styleProvider={this.sidebarStyleProvider}
docFilters={this.docFilters}
- scaleField={this.sidebarKey() + "-scale"}
+ scaleField={this.sidebarKey + "-scale"}
setHeight={(height) => this.props.setHeight(height + this.filtersHeight())}
isAnnotationOverlay={false}
select={emptyFunction}
@@ -132,7 +139,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
ScreenToLocalTransform={this.screenToLocalTransform}
renderDepth={this.props.renderDepth + 1}
viewType={CollectionViewType.Stacking}
- fieldKey={this.sidebarKey()}
+ fieldKey={this.sidebarKey}
pointerEvents={"all"}
/>
</div>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 32ddc140c..c9e532745 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -174,6 +174,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
}
case StyleProp.PointerEvents:
+ if (doc?.type === DocumentType.MARKER) return "none";
if (props?.pointerEvents === "none") return "none";
const layer = doc && props?.layerProvider?.(doc);
if (opacity() === 0 || (doc?.type === DocumentType.INK && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index 56346b68b..fd0ac9d5c 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -1,8 +1,50 @@
+@import "./global/globalCssVariables";
// this file is for overriding all the css from installed node modules
// goldenlayout stuff
div .lm_header {
background: $dark-gray;
+ overflow: hidden;
+ height: 27px !important;
+}
+
+/* Width */
+.lm_header::-webkit-scrollbar {
+ -webkit-appearance: none;
+ display: none;
+}
+
+/* Width */
+.lm_header:hover::-webkit-scrollbar {
+ -webkit-appearance: none;
+ display: block;
+ height: 0px;
+}
+
+/* Track */
+.lm_header:hover::-webkit-scrollbar-track {
+ -webkit-appearance: none;
+ display: none;
+}
+
+/* Handle */
+.lm_header:hover::-webkit-scrollbar-thumb {
+ -webkit-appearance: none;
+ background: $dark-gray;
+}
+
+/* Handle on hover */
+.lm_header:hover::-webkit-scrollbar-thumb:hover {
+ -webkit-appearance: none;
+ background: $dark-gray;
+}
+
+.lm_tabs {
+ display: flex;
+ position: absolute;
+ width: calc(100% - 60px);
+ overflow: scroll;
+ background: $dark-gray;
}
.lm_tab {
@@ -15,7 +57,14 @@ div .lm_header {
}
.lm_header .lm_controls {
- right: 1em !important;
+ align-items: center;
+ position: absolute;
+ background-color: $dark-gray;
+ border-radius: 5px;
+ display: flex;
+ justify-content: space-evenly;
+ height: 23px;
+ width: 65px;
}
// @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index d1b8b2df0..b2ee33807 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,40 +1,46 @@
-@import "../../views/global/globalCssVariables.scss";
+@import "../global/globalCssVariables.scss";
.lm_title {
- margin-top: 3px;
- border-radius: 5px;
- border: solid 0px dimgray;
- border-width: 2px 2px 0px;
- height: 20px;
- transform: translate(0px, -3px);
+ -webkit-appearance: none;
+ display: inline-block;
+ align-self: center;
+ align-items: center;
+ height: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ background: transparent;
+ border: solid 0px transparent;
cursor: grab;
+ color: $black;
}
.lm_title.focus-visible {
+ -webkit-appearance: none;
cursor: text;
}
.lm_title_wrap {
overflow: hidden;
- height: 19px;
- margin-top: -2px;
- display: inline-block;
+ align-items: center;
+ align-self: center;
+ background: transparent;
+ width: max-content;
+ height: 100%;
+ display: flex;
}
.lm_active .lm_title {
- border: solid 1px lightgray;
-}
-
-.lm_header .lm_tab .lm_close_tab {
- position: absolute;
- text-align: center;
+ -webkit-appearance: none;
+ // font-weight: 700;
}
.lm_header .lm_tab {
- padding-right: 20px;
- margin-top: -1px;
- border-bottom: 1px black;
+ padding: 0px;
+ opacity: 0.7;
+ box-shadow: none;
+ height: 24px;
+ // border-bottom: 1px black;
.collectionDockingView-gear {
display: none;
@@ -42,9 +48,13 @@
}
.lm_header .lm_tab.lm_active {
- padding-right: 20px;
- margin-top: 1px;
- border-bottom: unset;
+ padding: 0;
+ opacity: 1;
+ margin: 0;
+ box-shadow: none;
+ height: 27px;
+ margin-right: 2px;
+ // border-bottom: unset;
.collectionDockingView-gear {
display: inline-block;
@@ -78,6 +88,41 @@
position: relative;
}
+.lm_drag_tab {
+ padding: 0;
+ width: 15px !important;
+ height: 15px !important;
+ position: relative !important;
+ display: inline-flex !important;
+ align-items: center;
+ top: 0 !important;
+ right: unset !important;
+ left: 0 !important;
+}
+
+.lm_close_tab {
+ padding: 0;
+ align-self: center;
+ margin-right: 5px;
+ background-color: black;
+ border-radius: 3px;
+ opacity: 1 !important;
+ width: 15px !important;
+ height: 15px !important;
+ position: relative !important;
+ display: inline-flex !important;
+ align-items: center;
+ top: 0 !important;
+ right: unset !important;
+ left: 0 !important;
+}
+
+.lm_tab,
+.lm_tab_active {
+ display: flex !important;
+ padding-right: 0 !important;
+}
+
.collectiondockingview-container {
width: 100%;
height: 100%;
@@ -105,16 +150,17 @@
}
.lm_content {
- background: white;
+ background: $white;
}
.lm_controls>li {
- opacity: 0.6;
- transform: scale(1.2);
+ opacity: 1;
+ transform: scale(1);
}
.lm_controls .lm_popout {
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAAAAABHICnvAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfkCBsXMgbrEyzaAAAAT0lEQVQY02NgIAcIu8tgEW3/u4IDQ5B14/8LQlhFhckVFfCJjIyIOfP/QWpEZGSQJFS05s9fIPj3/z+YmseCTxS7CZS7DI+PsYcOjpAkDAA6H0KZxzDzlgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wOC0yN1QyMzo1MDowNi0wNDowMDvgVpQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDgtMjdUMjM6NTA6MDYtMDQ6MDBKve4oAAAAAElFTkSuQmCC)
+ transform: rotate(45deg);
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAQUlEQVR4nHXOQQ4AMAgCQeT/f6aXpsGK3jSTuCVJAAr7iBdoAwCKd0nwfaAdHbYERw5b44+E8JoBjEYGMBq5gAYP3usUDu2IvoUAAAAASUVORK5CYII=);
}
.lm_maximised .lm_controls .lm_maximise {
@@ -334,8 +380,6 @@
background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center;
}
- .flexlayout__popup_menu {}
-
.flexlayout__popup_menu_item {
padding: 2px 10px 2px 10px;
color: #ddd;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 388f9a909..c0d39b2a2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -4,7 +4,7 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import * as GoldenLayout from "../../../client/goldenLayout";
-import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, DocListCastAsync, DataSym } from "../../../fields/Doc";
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -24,6 +24,7 @@ import React = require("react");
import { DocumentType } from '../../documents/DocumentTypes';
import { listSpec } from '../../../fields/Schema';
import { LightboxView } from '../LightboxView';
+import { inheritParentAcls } from '../../../fields/util';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -160,6 +161,18 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
}
const instance = CollectionDockingView.Instance;
if (!instance) return false;
+ else {
+ const docList = DocListCast(instance.props.Document[DataSym]["data-all"]);
+ // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases
+ !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
+ // adds an alias of the doc to the data-all field of the layoutdocs of the aliases
+ DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => {
+ const aliasDocList = DocListCast(alias["data-all"]);
+ // if aliasDocList contains the alias, don't do anything
+ // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias
+ !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", alias !== instance.props.Document ? Doc.MakeAlias(document) : document);
+ });
+ }
const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
if (!pullSide && stack) {
@@ -381,15 +394,22 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
setTimeout(async () => {
const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
const tabs = sublists && Cast(sublists[0], Doc, null);
- const other = sublists && Cast(sublists[1], Doc, null);
+ // const other = sublists && Cast(sublists[1], Doc, null);
const tabdocs = await DocListCastAsync(tabs?.data);
- const otherdocs = await DocListCastAsync(other?.data);
- tabs && (Doc.GetProto(tabs).data = new List<Doc>(docs));
- const otherSet = new Set<Doc>();
- otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
- tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
- const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
- other && (Doc.GetProto(other).data = new List<Doc>(vals));
+ // const otherdocs = await DocListCastAsync(other?.data);
+ if (tabs) {
+ tabs.data = new List<Doc>(docs);
+ // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs)));
+ }
+ // const otherSet = new Set<Doc>();
+ // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+ // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
+ // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
+ // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]);
+ // if (other) {
+ // other.data = new List<Doc>(vals);
+ // // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals)));
+ // }
}, 0);
}
@@ -399,7 +419,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
}
tabCreated = (tab: any) => {
- tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
+ tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
}
stackCreated = (stack: any) => {
@@ -407,9 +427,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
if (e.target === stack.header?.element[0] && e.button === 2) {
const emptyPane = CurrentUserUtils.EmptyPane;
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
- }), "", stack);
+ const docToAdd = Docs.Create.FreeformDocument([], {
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
+ });
+ this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+ CollectionDockingView.AddSplit(docToAdd, "", stack);
}
});
@@ -430,9 +452,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
// stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size
const emptyPane = CurrentUserUtils.EmptyPane;
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
+ const docToAdd = Docs.Create.FreeformDocument([], {
_width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
- }), "", stack);
+ });
+ this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+ CollectionDockingView.AddSplit(docToAdd, "", stack);
}));
}
@@ -445,4 +469,4 @@ Scripting.addGlobal(function openInLightbox(doc: any) { LightboxView.AddDocTab(d
"opens up document in a lightbox", "(doc: any)");
Scripting.addGlobal(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); },
"opens up document in tab on right side of the screen", "(doc: any)");
-Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
+Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index ec8805907..46e40489b 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -20,43 +20,49 @@
}
.bottomPopup-background {
- padding-right: 14px;
+ background: $medium-blue;
+ display: flex;
+ border-radius: 10px;
height: 35;
- transform: translate3d(6px, 5px, 0px);
- padding-top: 6.5px;
- padding-bottom: 7px;
- padding-left: 5px;
+ transform: translate3d(6px, 0px, 0px);
+ align-content: center;
+ justify-content: center;
+ align-items: center;
}
.bottomPopup-text {
+ color: $white;
display: inline;
white-space: nowrap;
padding-left: 8px;
- padding-right: 4px;
+ padding-right: 20px;
vertical-align: middle;
font-size: 12.5px;
}
.bottomPopup-descriptions {
+ cursor:pointer;
display: inline;
white-space: nowrap;
padding-left: 8px;
padding-right: 8px;
vertical-align: middle;
- background-color: lightgrey;
- border-radius: 5.5px;
+ background-color: $light-gray;
+ border-radius: 3px;
color: black;
margin-right: 5px;
}
.bottomPopup-exit {
+ cursor:pointer;
display: inline;
white-space: nowrap;
+ margin-right: 10px;
padding-left: 8px;
padding-right: 8px;
vertical-align: middle;
- background-color: lightgrey;
- border-radius: 5.5px;
+ background-color: $close-red;
+ border-radius: 3px;
color: black;
}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index e0b90304b..52c836556 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -167,24 +167,22 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
})}
</div>
{DocumentLinksButton.StartLink ? <span className="bottomPopup-background" style={{
- background: backgroundColor === color ? "black" : backgroundColor,
pointerEvents: "all"
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}
+ Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
</span>
- <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
- "Turn on description pop-up"} </div></>} placement="top">
+ <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
<span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
</span>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">Exit link clicking mode </div></>} placement="top">
+ <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
<span className="bottomPopup-exit" onClick={this.exitLongLinks}>
- Clear
+ Stop
</span>
</Tooltip>
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index c0fc774d3..f04b19ef7 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -38,10 +38,10 @@
border: unset;
.collectionMenu-divider {
- height: 85%;
+ height: 100%;
margin-left: 3px;
margin-right: 3px;
- width: 1.5px;
+ width: 2px;
background-color: $medium-gray;
}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 6e6fabd0d..8f4df4a92 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -29,7 +29,7 @@ import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
-import { PresBox } from "../nodes/PresBox";
+import { PresBox } from "../nodes/trails/PresBox";
import "./CollectionMenu.scss";
import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { TabDocView } from "./TabDocView";
@@ -665,7 +665,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
}
@computed get drawButtons() {
- const func = action((i: number, keep: boolean) => {
+ const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => {
this._keepPrimitiveMode = keep;
if (this._selectedPrimitive !== i) {
this._selectedPrimitive = i;
@@ -683,13 +683,14 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
GestureOverlay.Instance.InkShape = "";
SetActiveBezierApprox("0");
}
+ e.stopPropagation();
});
return <div className="btn-draw" key="draw">
{this._draw.map((icon, i) =>
<Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom">
<button className="antimodeMenu-button"
- onPointerDown={() => func(i, false)}
- onDoubleClick={() => func(i, true)}
+ onPointerDown={e => func(e, i, false)}
+ onDoubleClick={e => func(e, i, true)}
style={{ backgroundColor: i === this._selectedPrimitive ? "525252" : "", fontSize: "20" }}>
<FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
</button>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index e1e04915a..380f82813 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -413,8 +413,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
isContentActive={returnTrue}
isDocumentActive={returnFalse}
ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.docFilters}
- docRangeFilters={this.docRangeFilters}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
styleProvider={DefaultStyleProvider}
layerProvider={undefined}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 7aa8dfd56..b9bc83d49 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -239,10 +239,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={stackedDocTransform}
focus={this.focusDocument}
- docFilters={this.docFilters}
+ docFilters={this.childDocFilters}
hideDecorationTitle={this.props.childHideDecorationTitle?.()}
hideTitle={this.props.childHideTitle?.()}
- docRangeFilters={this.docRangeFilters}
+ docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a5d27f038..2f07c0241 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -8,7 +8,7 @@ import { ScriptField } from "../../../fields/ScriptField";
import { WebField } from "../../../fields/URLField";
import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { Utils, returnFalse } from "../../../Utils";
+import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Networking } from "../../Network";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -22,6 +22,7 @@ import ReactLoading from 'react-loading';
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
+ SetSubView?: (subView: any) => void;
}
export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: X) {
@@ -30,6 +31,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
private gestureDisposer?: GestureUtils.GestureEventDisposer;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _mainCont?: HTMLDivElement;
+ @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it
+ @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer?.();
this.gestureDisposer?.();
@@ -45,6 +48,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.createDashEventsTarget(ele);
}
+ componentDidMount() {
+ this.props.SetSubView?.(this);
+ }
+
componentWillUnmount() {
this.gestureDisposer?.();
this._multiTouchDisposer?.();
@@ -73,23 +80,21 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
const { Document, DataDoc } = this.props;
const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc)).
- filter(pair => { // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys
- return pair.layout && /*!pair.layout.hidden &&*/ (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length));
+ filter(pair => { // filter out any documents that have a proto that we don't have permissions to
+ return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
});
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
- docFilters = () => {
- return [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
- }
- docRangeFilters = () => {
- return [...this.props.docRangeFilters(), ...Cast(this.props.Document._docRangeFilters, listSpec("string"), [])];
- }
- searchFilterDocs = () => {
- return [...this.props.searchFilterDocs(), ...DocListCast(this.props.Document._searchFilterDocs)];
- }
+ collectionFilters = () => this._focusFilters ?? Cast(this.props.Document._docFilters, listSpec("string"), []);
+ collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+ childDocFilters = () => [...this.props.docFilters(), ...this.collectionFilters()];
+ childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()];
+ IsFiltered = () => this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
+ this.props.docFilters().length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined
+ searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
@computed.struct get childDocs() {
TraceMobx();
let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -108,8 +113,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
- const docFilters = this.docFilters();
- const docRangeFilters = this.docRangeFilters();
+ const docFilters = this.childDocFilters();
+ const docRangeFilters = this.childDocRangeFilters();
const searchDocs = this.searchFilterDocs();
if (this.props.Document.dontRegisterView || (!docFilters.length && !docRangeFilters.length && !searchDocs.length)) {
return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
@@ -303,7 +308,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
} else {
const path = window.location.origin + "/doc/";
if (text.startsWith(path)) {
- const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ const docid = text.replace(Doc.globalServerPath(), "").split("?")[0];
DocServer.GetRefField(docid).then(f => {
if (f instanceof Doc) {
if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index f41043179..292dfd77c 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -32,12 +32,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
@observable _collapsed: boolean = false;
@observable _childClickedScript: Opt<ScriptField>;
@observable _viewDefDivClick: Opt<ScriptField>;
- @observable _focusDocFilters: Opt<string[]>; // fields that get overridden by a focus anchor
@observable _focusPivotField: Opt<string>;
- @observable _focusRangeFilters: Opt<string[]>;
getAnchor = () => {
- const anchor = Docs.Create.TextanchorDocument({
+ const anchor = Docs.Create.HTMLAnchorDocument([], {
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.rootDoc
});
@@ -72,9 +70,9 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
@action
setViewSpec = (anchor: Doc, preview: boolean) => {
if (preview) { // if in preview, then override document's fields with view spec
+ this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
+ this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
this._focusPivotField = StrCast(anchor.pivotField);
- this._focusDocFilters = StrListCast(anchor.docFilters);
- this._focusRangeFilters = StrListCast(anchor.docRangeFilters);
} else if (anchor.pivotField !== undefined) { // otherwise set document's fields based on anchor view spec
this.layoutDoc._prevFilterIndex = 1;
this.layoutDoc._pivotField = StrCast(anchor.pivotField);
@@ -84,8 +82,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
return 0;
}
- pivotDocFilters = () => this._focusDocFilters || this.props.docFilters();
- pivotDocRangeFilters = () => this._focusRangeFilters || this.props.docRangeFilters();
layoutEngine = () => this._layoutEngine;
toggleVisibility = action(() => this._collapsed = !this._collapsed);
@@ -139,10 +135,8 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }}
onClick={this.contentsDown}>
<CollectionFreeFormView {...this.props}
- engineProps={{ pivotField: this.pivotField, docFilters: this.docFilters, docRangeFilters: this.docRangeFilters }}
+ engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
fitContentsToDoc={returnTrue}
- docFilters={this.pivotDocFilters}
- docRangeFilters={this.pivotDocRangeFilters}
childClickScript={this._childClickedScript}
viewDefDivClick={this._viewDefDivClick}
childFreezeDimensions={true}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e225c4a11..e65ebf075 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { computed, observable, runInAction } from 'mobx';
+import { computed, observable, runInAction, action } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
@@ -236,13 +236,16 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
* Shows the filter icon if it's a user-created collection which isn't a dashboard and has some docFilters applied on it or on the current dashboard.
*/
@computed get showFilterIcon() {
- return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && ((StrListCast(this.props.Document._docFilters).length || StrListCast(this.props.Document._docRangeFilters).length || StrListCast(CurrentUserUtils.ActiveDashboard._docFilters).length || StrListCast(CurrentUserUtils.ActiveDashboard._docRangeFilters).length));
+ return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && this._subView?.IsFiltered();
}
+ @observable _subView: any = undefined;
+
render() {
TraceMobx();
const props: SubCollectionViewProps = {
...this.props,
+ SetSubView: action((subView: any) => this._subView = subView),
addDocument: this.addDocument,
moveDocument: this.moveDocument,
removeDocument: this.removeDocument,
@@ -260,8 +263,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
{this.showFilterIcon ?
<FontAwesomeIcon icon={"filter"} size="lg"
- style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: '#18c718bd', zIndex: 1 }}
- onPointerDown={e => { runInAction(() => CurrentUserUtils.propertiesWidth = 250); e.stopPropagation(); }}
+ style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
+ onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
/>
: (null)}
</div>);
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 9acbc4f85..a963f1cb9 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,19 +1,62 @@
input.lm_title:focus,
-input.lm_title
-{
+input.lm_title {
max-width: unset !important;
+ outline: none;
transition-delay: unset;
- width: 100%;
+ width: max-content;
cursor: text;
}
+
input.lm_title {
transition-delay: 0.35s;
- width: 100px;
+ width: max-content;
cursor: pointer;
}
-.tabDocView-drag {
- margin: auto;
+
+.lm_iconWrap {
+ display: flex;
+ color: black;
+ width: 15px;
+ height: 15px;
+ align-items: center;
+ align-self: center;
+ justify-content: center;
+ margin: 3px;
+ border-radius: 20%;
+
+ .moreInfoDot {
+ background-color: white;
+ border-radius: 100%;
+ width: 3px;
+ height: 3px;
+ margin: 0.5px;
+ }
+}
+
+.ffMenu {
+ display: grid;
+ grid-auto-rows: 35px;
+ grid-auto-columns: auto auto auto auto auto;
+ right: 10px;
+ bottom: 50px;
+ position: absolute;
+ min-height: 35px;
+ height: max-content;
+ border: solid 2px black;
+ border-radius: 5px;
+ background-color: #bddbe6;
+ width: max-content;
+ min-width: 35px;
+
+ .ffMenuButton {
+ display: flex;
+ width: 35px;
+ height: 35px;
+ align-items: center;
+ justify-content: center;
+ }
}
+
.miniMap-hidden,
.miniMap {
position: absolute;
@@ -37,6 +80,7 @@ input.lm_title {
}
}
}
+
.miniMap-hidden {
position: absolute;
bottom: 0;
@@ -46,7 +90,8 @@ input.lm_title {
transform: translate(20px, 20px) rotate(45deg);
border-radius: 30px;
padding: 2px;
- > svg {
+
+ >svg {
margin-top: 3px;
transform: translate(0px, 7px);
}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 7e2f7811e..1969d728c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -1,3 +1,4 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import 'golden-layout/src/css/goldenlayout-base.css';
@@ -9,9 +10,9 @@ import * as ReactDOM from 'react-dom';
import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Id } from '../../../fields/FieldSymbols';
import { FieldId } from "../../../fields/RefField";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -24,15 +25,15 @@ import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { LightboxView } from '../LightboxView';
import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
-import { FieldViewProps } from '../nodes/FieldView';
-import { PinProps, PresBox, PresMovement } from '../nodes/PresBox';
+import { PinProps, PresBox, PresMovement } from '../nodes/trails';
import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionViewType, CollectionView } from './CollectionView';
+import { CollectionView, CollectionViewType } from './CollectionView';
import "./TabDocView.scss";
import React = require("react");
+import Color = require('color');
const _global = (window /* browser */ || global /* node */) as any;
interface TabDocViewProps {
@@ -52,6 +53,14 @@ export class TabDocView extends React.Component<TabDocViewProps> {
@computed get layoutDoc() { return this._document && Doc.Layout(this._document); }
@computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); }
+ @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? "black" : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); }
+ // @computed get renderBounds() {
+ // const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+ // const xbounds = bounds[2] - bounds[0];
+ // const ybounds = bounds[3] - bounds[1];
+ // const dim = Math.max(xbounds, ybounds);
+ // return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+ // }
get stack() { return (this.props as any).glContainer.parent.parent; }
get tab() { return (this.props as any).glContainer.tab; }
@@ -65,15 +74,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
tab.DashDoc = doc;
CollectionDockingView.Instance.tabMap.add(tab);
-
+ const iconType: IconProp = Doc.toIcon(doc);
// setup the title element and set its size according to the # of chars in the title. Show the full title when clicked.
const titleEle = tab.titleElement[0];
+ const iconWrap = document.createElement("div");
+ const closeWrap = document.createElement("div");
+
+
titleEle.size = StrCast(doc.title).length + 3;
titleEle.value = doc.title;
titleEle.onchange = undoBatch(action((e: any) => {
titleEle.size = e.currentTarget.value.length + 3;
Doc.GetProto(doc).title = e.currentTarget.value;
}));
+
+ const dragBtnDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction);
+ };
+
+
if (tab.element[0].children[1].children.length === 1) {
const toggle = document.createElement("div");
toggle.style.width = "10px";
@@ -83,18 +102,42 @@ export class TabDocView extends React.Component<TabDocViewProps> {
toggle.style.borderTopRightRadius = "7px";
toggle.style.position = "relative";
toggle.style.display = "inline-block";
- toggle.style.background = "gray";
- toggle.style.borderLeft = "solid 1px black";
+ toggle.style.background = "transparent";
toggle.onclick = (e: MouseEvent) => {
if (tab.contentItem === tab.header.parent.getActiveContentItem()) {
tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background;
}
};
- tab.element[0].style.borderTopRightRadius = "8px";
- tab.element[0].children[1].appendChild(toggle);
- tab._disposers.layerDisposer = reaction(() =>
- ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
- ({ layer, color }) => toggle.style.background = !layer ? color : "dimgrey", { fireImmediately: true });
+ iconWrap.className = "lm_iconWrap";
+ iconWrap.id = "lm_iconWrap";
+ closeWrap.className = "lm_iconWrap";
+ closeWrap.id = "lm_closeWrap";
+ closeWrap.onclick = (e: MouseEvent) => {
+ tab.header.parent.contentItem.remove();
+ Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true);
+ };
+ const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
+ const closeIcon = <FontAwesomeIcon icon={"times"} />;
+ ReactDOM.render(docIcon, iconWrap);
+ ReactDOM.render(closeIcon, closeWrap);
+ // tab.element[0].append(closeWrap);
+ tab.element[0].prepend(iconWrap);
+ tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
+ ({ layer, color }) => {
+ const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
+ titleEle.style.color = textColor;
+ titleEle.style.backgroundColor = "transparent";
+ iconWrap.style.color = textColor;
+ closeWrap.style.color = textColor;
+ moreInfoDrag.style.backgroundColor = textColor;
+ tab.element[0].style.background = !layer ? color : "dimgrey";
+ }, { fireImmediately: true });
+ // TODO:glr fix
+ // tab.element[0].style.borderTopRightRadius = "8px";
+ // tab.element[0].children[1].appendChild(toggle);
+ // tab._disposers.layerDisposer = reaction(() =>
+ // ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
+ // ({ layer, color }) => toggle.style.background = !layer ? color : "dimgrey", { fireImmediately: true });
}
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: MouseEvent) => {
@@ -103,13 +146,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
tab.setActive(true);
}
};
- const dragBtnDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([dragHdl], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction);
- };
+
// select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
titleEle.onpointerdown = action((e: any) => {
- if (e.target.className !== "lm_close_tab") {
+ if (e.target.className !== "lm_iconWrap") {
if (this.view) SelectionManager.SelectView(this.view, false);
else this._activated = true;
if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
@@ -123,25 +164,29 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const toggle = tab.element[0].children[1].children[0] as HTMLInputElement;
selected && tab.contentItem !== tab.header.parent.getActiveContentItem() &&
UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), "tab switch");
- toggle.style.fontWeight = selected ? "bold" : "";
- toggle.style.textTransform = selected ? "uppercase" : "";
+ // toggle.style.fontWeight = selected ? "bold" : "";
+ // toggle.style.textTransform = selected ? "uppercase" : "";
}));
//attach the selection doc buttons menu to the drag handle
- const stack = tab.contentItem.parent;
- const dragHdl = document.createElement("div");
- dragHdl.className = "lm_drag_tab";
+ const stack: HTMLDivElement = tab.contentItem.parent;
+ const header: HTMLDivElement = tab;
+ stack.onscroll = action((e: any) => {
+ console.log('scrolling...');
+ });
+ const moreInfoDrag = document.createElement("div");
+ moreInfoDrag.className = "lm_iconWrap";
tab._disposers.buttonDisposer = reaction(() => this.view, view =>
- view && [ReactDOM.render(<span className="tabDocView-drag" onPointerDown={dragBtnDown}><CollectionDockingViewMenu views={() => [view]} Stack={stack} /></span>, dragHdl), tab._disposers.buttonDisposer?.()],
+ view && [ReactDOM.render(<span><CollectionDockingViewMenu views={() => [view]} Stack={stack} /></span>, moreInfoDrag), tab._disposers.buttonDisposer?.()],
{ fireImmediately: true });
- tab.reactComponents = [dragHdl];
- tab.closeElement.before(dragHdl);
+ // tab.reactComponents = [moreInfoDrag];
+ // tab.element[0].children[3].before(moreInfoDrag);
// highlight the tab when the tab document is brushed in any part of the UI
tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
titleEle.value = title;
- titleEle.style.padding = degree ? 0 : 2;
- titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
+ // titleEle.style.padding = degree ? 0 : 2;
+ // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
}, { fireImmediately: true });
// clean up the tab when it is closed
@@ -221,9 +266,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
})).observe(this.props.glContainer._element[0]);
this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
- this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
- ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
- { fireImmediately: true });
+ // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
+ // ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
+ // { fireImmediately: true });
}
componentWillUnmount() {
@@ -243,10 +288,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
// adds a tab to the layout based on the locaiton parameter which can be:
- // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab,
+ // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab,
// add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
- // replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
- // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
+ // replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
+ // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
// "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
// inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
addDocTab = (doc: Doc, location: string) => {
@@ -317,8 +362,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
PanelHeight={this.PanelHeight}
layerProvider={this.layerProvider}
styleProvider={DefaultStyleProvider}
- docFilters={CollectionDockingView.Instance.docFilters}
- docRangeFilters={CollectionDockingView.Instance.docRangeFilters}
+ docFilters={CollectionDockingView.Instance.childDocFilters}
+ docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
addDocument={undefined}
removeDocument={undefined}
@@ -422,6 +467,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
<div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
<CollectionFreeFormView
Document={this.props.document}
+ SetSubView={() => this}
CollectionView={undefined}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
@@ -450,8 +496,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
layerProvider={undefined}
addDocTab={this.props.addDocTab}
pinToPres={TabDocView.PinDoc}
- docFilters={CollectionDockingView.Instance.docFilters}
- docRangeFilters={CollectionDockingView.Instance.docRangeFilters}
+ docFilters={CollectionDockingView.Instance.childDocFilters}
+ docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
fitContentsToDoc={returnTrue}
/>
@@ -460,4 +506,4 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
</div>
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 401bdcb02..206e48aac 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -151,7 +151,10 @@ export class TreeView extends React.Component<TreeViewProps> {
this.treeViewOpen = !this.treeViewOpen;
} else {
// choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
+ // this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
+ // choose an appropriate alias or make one -- -- choose the first alias that (1) the user owns, (2) has no context field - if I own it and someone else does not have it open,, otherwise create an alias
this.props.addDocTab(this.props.document, "add:right");
+
}
}
constructor(props: any) {
@@ -508,9 +511,10 @@ export class TreeView extends React.Component<TreeViewProps> {
Doc.IsSystem(this.doc) ? [] :
this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
[{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }, makeFolder] :
- [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }];
+ [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }, { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }];
}
onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
+
onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a4e310e6c..fa7e75202 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,7 +38,7 @@ import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDo
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
-import { PresBox } from "../../nodes/PresBox";
+import { PresBox } from "../../nodes/trails/PresBox";
import { StyleLayers, StyleProp } from "../../StyleProvider";
import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
@@ -48,6 +48,8 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { DateField } from "../../../../fields/DateField";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -109,8 +111,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef = React.createRef<HTMLDivElement>();
@observable _keyframeEditing = false;
- @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it
- @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
@@ -158,8 +158,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.layoutDoc._viewScale = vals.scale;
}
freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
- freeformDocFilters = () => this._focusFilters || this.docFilters();
- freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters();
reverseNativeScaling = () => this.fitToContent ? true : false;
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX);
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY);
@@ -260,6 +258,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
}
+ d._lastModified = new DateField();
const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
@@ -834,10 +833,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
map(doc => ({ ...this.childDataProvider(doc, ""), ...this.childSizeProvider(doc, "") }));
if (measuredDocs.length) {
const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
- ({
- xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
- yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
- })
+ ({
+ xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
+ yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+ })
, {
xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
@@ -1029,8 +1028,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
ScreenToLocalTransform={childLayout.z ? this.getTransformOverlay : this.getTransform}
PanelWidth={childLayout[WidthSym]}
PanelHeight={childLayout[HeightSym]}
- docFilters={this.freeformDocFilters}
- docRangeFilters={this.freeformRangeDocFilters}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
isContentActive={this.isAnnotationOverlay ? this.props.isContentActive : returnFalse}
isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
@@ -1196,14 +1195,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (preview) {
this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
- } else if (anchor.pivotField !== undefined) {
- this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
- this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
+ } else {
+ if (anchor.docFilters) {
+ this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
+ }
+ if (anchor.docRangeFilters) {
+ this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
+ }
}
return 0;
}
getAnchor = () => {
+ if (this.props.Document.annotationOn) {
+ return this.rootDoc;
+ }
const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc });
const proto = Doc.GetProto(anchor);
proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
@@ -1486,7 +1492,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents: this.backgroundEvents ? "all" : this.props.pointerEvents as any,
+ pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
+ this.backgroundEvents ? "all" : this.props.pointerEvents as any,
transform: `scale(${this.contentScaling || 1})`,
width: `${100 / (this.contentScaling || 1)}%`,
height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 1f4fcb2a5..19da7ea00 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,6 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
@@ -19,7 +19,8 @@ import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { ContextMenu } from "../../ContextMenu";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { PresBox, PresMovement } from "../../nodes/PresBox";
+import { PresBox } from "../../nodes/trails/PresBox";
+import { PresMovement } from "../../nodes/trails/PresEnums";
import { PreviewCursor } from "../../PreviewCursor";
import { CollectionDockingView } from "../CollectionDockingView";
import { SubCollectionViewProps } from "../CollectionSubView";
@@ -297,7 +298,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = x;
this._downY = y;
const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) {
+ if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
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/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index f3a39a262..65c345547 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -237,8 +237,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
- docFilters={this.docFilters}
- docRangeFilters={this.docRangeFilters}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 29cb3511a..30836854a 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -236,9 +236,9 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
- docFilters={this.docFilters}
+ docFilters={this.childDocFilters}
isContentActive={returnFalse}
- docRangeFilters={this.docRangeFilters}
+ docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index 0c434eae5..fd99abce5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -103,6 +103,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
+ console.log("click cell");
let url: string;
if (url = StrCast(this.props.rowProps.row.href)) {
try {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 585cda729..fed64b620 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -413,8 +413,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
isContentActive={returnTrue}
isDocumentActive={returnFalse}
ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.docFilters}
- docRangeFilters={this.docRangeFilters}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
styleProvider={DefaultStyleProvider}
layerProvider={undefined}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index ead5e166e..7556f8b8a 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -1,15 +1,17 @@
-@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
+@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
// colors
$white: #ffffff;
-$light-gray:#dfdfdf;
-$medium-gray: #9F9F9F;
+$light-gray: #dfdfdf;
+$medium-gray: #9f9f9f;
$dark-gray: #323232;
$black: #000000;
-$light-blue: #BDDDF5;
-$medium-blue: #4476F7;
-$pink: #E0217D;
-$yellow: #F5D747;
+$light-blue: #bdddf5;
+$medium-blue: #4476f7;
+$pink: #e0217d;
+$yellow: #f5d747;
+
+$close-red: #e48282;
$drop-shadow: "#32323215";
@@ -21,10 +23,8 @@ $large-padding: 32px;
//icon sizes
$icon-size: 28px;
-$antimodemenu-height: 36px;
-
// fonts
-$sans-serif: "Noto Sans", sans-serif;
+$sans-serif: "Roboto", sans-serif;
$large-header: 16px;
$body-text: 12px;
$small-text: 9px;
@@ -33,18 +33,23 @@ $small-text: 9px;
// misc values
$border-radius: 0.3em;
$search-thumnail-size: 130;
+$topbar-height: 32px;
+$antimodemenu-height: 36px;
// dragged items
$contextMenu-zindex: 100000; // context menu shows up over everything
$radialMenu-zindex: 100000; // context menu shows up over everything
+// borders
+$standard-border: solid 1px #9f9f9f;
+
$searchpanel-height: 32px;
$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
$docDecorations-zindex: 998; // then doc decorations appear over everything else
$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
$COLLECTION_BORDER_WIDTH: 0;
$SCHEMA_DIVIDER_WIDTH: 4;
-$MINIMIZED_ICON_SIZE:24;
+$MINIMIZED_ICON_SIZE: 24;
$MAX_ROW_HEIGHT: 44px;
$DFLT_IMAGE_NATIVE_DIM: 900px;
$MENU_PANEL_WIDTH: 60px;
@@ -62,4 +67,4 @@ $TREE_BULLET_WIDTH: 20px;
DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
MENU_PANEL_WIDTH: $MENU_PANEL_WIDTH;
TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
-} \ No newline at end of file
+}
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 1e0381c33..2aeb8e338 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -31,4 +31,8 @@ export enum Padding {
export enum IconSizes {
ICON_SIZE = "28px",
+}
+
+export enum Borders {
+ STANDARD = "solid 1px #9F9F9F"
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index 839ebf894..e45a91d57 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -22,7 +22,7 @@
.linkEditor-info {
//border-bottom: 0.5px solid $light-gray;
//padding-bottom: 1px;
- padding-top: 5px;
+ padding: 12px;
padding-left: 5px;
//margin-bottom: 6px;
display: flex;
@@ -61,7 +61,7 @@
}
.linkEditor-description {
- padding-left: 6.5px;
+ padding-left: 26px;
padding-right: 6.5px;
padding-bottom: 3.5px;
@@ -107,9 +107,9 @@
}
.linkEditor-followingDropdown {
- padding-left: 6.5px;
+ padding-left: 26px;
padding-right: 6.5px;
- padding-bottom: 6px;
+ padding-bottom: 15px;
&:hover {
cursor: pointer;
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index a2ea42999..19c6463d3 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -7,20 +7,19 @@
z-index: 2001;
.linkMenu-list,
- .linkMenu-listEditor
- {
+ .linkMenu-listEditor {
display: inline-block;
position: relative;
- border: 1px solid black;
- box-shadow: 3px 3px 1.5px grey;
+ border: 1px solid #e4e4e4;
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
background: white;
-
min-width: 170px;
- max-height: 170px;
+ max-height: 230px;
overflow-y: scroll;
z-index: 10;
- }
- .linkMenu-list {
+ }
+
+ .linkMenu-list {
white-space: nowrap;
overflow-x: hidden;
width: 240px;
@@ -46,13 +45,13 @@
}
.linkMenu-group-name {
+ padding: 10px;
&:hover {
- p {
- background-color: lightgray;
-
- }
+ // p {
+ // background-color: lightgray;
+ // }
p.expand-one {
width: calc(100% + 20px);
@@ -65,10 +64,9 @@
p {
width: 100%;
- //padding: 4px 6px;
line-height: 12px;
border-radius: 5px;
- font-weight: bold;
+ text-transform: capitalize;
}
.linkEditor-tableButton {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c7888c5ee..6fc860447 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -15,6 +15,9 @@ interface Props {
changeFlyout: () => void;
}
+/**
+ * the outermost component for the link menu of a node that contains a list of its linked nodes
+ */
@observer
export class LinkMenu extends React.Component<Props> {
private _editorRef = React.createRef<HTMLDivElement>();
@@ -36,6 +39,11 @@ export class LinkMenu extends React.Component<Props> {
}
}
+ /**
+ * maps each link to a JSX element to be rendered
+ * @param groups LinkManager containing info of all of the links
+ * @returns list of link JSX elements if there at least one linked element
+ */
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
const linkItems = Array.from(groups.entries()).map(group =>
<LinkMenuGroup
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 74af78234..c7586a467 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -40,7 +40,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
<div className="linkMenu-group-name">
- <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
+ <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p>
</div>
<div className="linkMenu-group-wrapper">
{groupItems}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 4f9881565..90722daf9 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,7 +4,7 @@
// border-top: 0.5px solid $medium-gray;
position: relative;
display: flex;
- border-bottom: 0.5px solid black;
+ border-top: 0.5px solid #cdcdcd;
padding-left: 6.5px;
padding-right: 2px;
@@ -55,8 +55,8 @@
.linkMenu-destination-title {
text-decoration: none;
- color: rgb(85, 120, 196);
- font-size: 14px;
+ color: #4476F7;
+ font-size: 16px;
padding-bottom: 2px;
padding-right: 4px;
margin-right: 4px;
@@ -76,7 +76,7 @@
text-decoration: none;
font-style: italic;
color: rgb(95, 97, 102);
- font-size: 10px;
+ font-size: 9px;
margin-left: 20px;
max-width: 125px;
height: auto;
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
new file mode 100644
index 000000000..8ae65158d
--- /dev/null
+++ b/src/client/views/linking/LinkPopup.scss
@@ -0,0 +1,45 @@
+.linkPopup-container {
+ background: white;
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
+ top: 35px;
+ height: 200px;
+ width: 200px;
+ position: absolute;
+ padding: 15px;
+ border-radius: 3px;
+
+ input {
+ border: 1px solid #b9b9b9;
+ border-radius: 20px;
+ height: 25px;
+ width: 100%;
+ padding-left: 10px;
+ }
+
+ .divider {
+ margin: 10px 0;
+ height: 20px;
+ width: 100%;
+
+ .line {
+ height: 1px;
+ background-color: #b9b9b9;
+ width: 100%;
+ position: relative;
+ top: 12px;
+ }
+
+ .divider-text {
+ width: 20px;
+ background-color: white;
+ text-align: center;
+ position: relative;
+ margin: auto;
+ }
+ }
+
+
+ .searchBox-container {
+ background: pink;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
new file mode 100644
index 000000000..df469c53b
--- /dev/null
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -0,0 +1,113 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, observable, runInAction } from 'mobx';
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Cast, StrCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { emptyFunction, setupMoveUpEvents, returnFalse, returnTrue, returnEmptyDoclist, returnEmptyFilter } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager } from '../../util/DragManager';
+import { Hypothesis } from '../../util/HypothesisUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { undoBatch } from '../../util/UndoManager';
+import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
+import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import './LinkPopup.scss';
+import React = require("react");
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { Transform } from '../../util/Transform';
+import { DocUtils } from '../../documents/Documents';
+import { SearchBox } from '../search/SearchBox';
+import { EditorView } from 'prosemirror-view';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+
+interface LinkPopupProps {
+ showPopup: boolean;
+ // groupType: string;
+ // linkDoc: Doc;
+ // docView: DocumentView;
+ // sourceDoc: Doc;
+}
+
+/**
+ * Popup component for creating links from text to Dash documents
+ */
+
+@observer
+export class LinkPopup extends React.Component<LinkPopupProps> {
+ @observable private linkURL: string = "";
+ @observable public view?: EditorView;
+
+
+
+ // TODO: should check for valid URL
+ @undoBatch
+ makeLinkToURL = (target: string, lcoation: string) => {
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
+ }
+
+ @action
+ onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.linkURL = e.target.value;
+ }
+
+
+ getPWidth = () => 500;
+ getPHeight = () => 500;
+
+ render() {
+ const popupVisibility = this.props.showPopup ? "block" : "none";
+ return (
+ <div className="linkPopup-container" style={{ display: popupVisibility }}>
+ <div className="linkPopup-url-container">
+ <input autoComplete="off" type="text" value={this.linkURL} placeholder="Enter URL..." onChange={this.onLinkChange} />
+ <button onPointerDown={e => this.makeLinkToURL(this.linkURL, "add:right")}
+ style={{ display: "block", margin: "10px auto", }}>Apply hyperlink</button>
+ </div>
+ <div className="divider">
+ <div className="line"></div>
+ <p className="divider-text">or</p>
+ </div>
+ <div className="linkPopup-document-search-container">
+ {/* <i></i>
+ <input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input"
+ className="linkPopup-searchBox searchBox-input" /> */}
+
+ <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
+ DataDoc={CurrentUserUtils.MySearchPanelDoc}
+ fieldKey="data"
+ dropAction="move"
+ isSelected={returnTrue}
+ isContentActive={returnTrue}
+ select={returnTrue}
+ setHeight={returnFalse}
+ addDocument={undefined}
+ addDocTab={returnTrue}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={this.getPWidth}
+ PanelHeight={this.getPHeight}
+ renderDepth={0}
+ focus={DocUtils.DefaultFocus}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index a2e36f12e..82bad971d 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -196,7 +196,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
- this.props.Document[this.props.fieldKey] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client));
+ this.props.Document[this.props.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._recordStart = new Date().getTime();
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 9b75cd8f9..3d2cdf5a4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -11,7 +11,7 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
import { CollectionSchemaView } from "../collections/collectionSchema/CollectionSchemaView";
import { CollectionView } from "../collections/CollectionView";
import { InkingStroke } from "../InkingStroke";
-import { PresElementBox } from "../presentationview/PresElementBox";
+import { PresElementBox } from "../nodes/trails/PresElementBox";
import { SearchBox } from "../search/SearchBox";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
@@ -32,7 +32,7 @@ import { LabelBox } from "./LabelBox";
import { LinkAnchorBox } from "./LinkAnchorBox";
import { LinkBox } from "./LinkBox";
import { PDFBox } from "./PDFBox";
-import { PresBox } from "./PresBox";
+import { PresBox } from "./trails/PresBox";
import { ScreenshotBox } from "./ScreenshotBox";
import { ScriptingBox } from "./ScriptingBox";
import { SliderBox } from "./SliderBox";
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index daffaf9e7..b37b68249 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -1,16 +1,27 @@
@import "../global/globalCssVariables.scss";
+.documentLinksButton-menu {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ align-items: center;
+}
+
.documentLinksButton-cont {
min-width: 20;
min-height: 20;
position: absolute;
}
+
.documentLinksButton,
.documentLinksButton-endLink,
.documentLinksButton-startLink {
- height: 20px;
- width: 20px;
+ height: 25px;
+ width: 25px;
position: absolute;
border-radius: 50%;
opacity: 0.9;
@@ -33,27 +44,43 @@
}
.documentLinksButton {
- background-color: black;
+ background-color: $dark-gray;
+ color: $white;
font-weight: bold;
+ width: 80%;
+ height: 80%;
+ font-size: 100%;
+ transition: 0.2s ease all;
&:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
+ background-color: $black;
}
}
-.documentLinksButton-endLink {
- border: red solid 2px;
+.documentLinksButton.startLink {
+ background-color: $medium-blue;
+ color: $white;
+ font-weight: bold;
+ width: 80%;
+ height: 80%;
+ font-size: 100%;
+ transition: 0.2s ease all;
&:hover {
- background: deepskyblue;
- transform: scale(1.05);
- cursor: pointer;
+ background-color: $black;
}
}
-.documentLinksButton-startLink {
- border: red solid 2px;
- background-color: rgba(255, 192, 203, 0.5);
+.documentLinksButton-endLink {
+ border: $medium-blue 2px dashed;
+ color: $medium-blue;
+ background-color: none !important;
+ width: 80%;
+ height: 80%;
+ font-size: 100%;
+ transition: 0.2s ease all;
+
+ &:hover {
+ background-color: $light-blue;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index a6d07374a..7648e866e 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -20,6 +20,7 @@ import './DocumentLinksButton.scss';
import { DocServer } from "../../DocServer";
import { LightboxView } from "../LightboxView";
import { cat } from "shelljs";
+import { Colors } from "../global/globalEnums";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -30,12 +31,12 @@ interface DocumentLinksButtonProps {
Offset?: (number | undefined)[];
AlwaysOn?: boolean;
InMenu?: boolean;
- StartLink?: boolean;
+ StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
- @observable public static StartLink: Doc | undefined;
+ @observable public static StartLink: Doc | undefined; //origin's Doc, if defined
@observable public static StartLinkView: DocumentView | undefined;
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
@@ -45,6 +46,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
public static invisibleWebRef = React.createRef<HTMLDivElement>();
@action public static ClearLinkEditor() { DocumentLinksButton.LinkEditorDocView = undefined; }
+
@action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
if (this.props.InMenu && this.props.StartLink) {
@@ -66,6 +68,40 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return false;
}
+ onLinkMenuOpen = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
+ if (doubleTap) {
+ const rootDoc = this.props.View.rootDoc;
+ const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish";
+ DocServer.GetRefField(docid).then(async docx => {
+ const rootAlias = () => {
+ const rootAlias = Doc.MakeAlias(rootDoc);
+ rootAlias.x = rootAlias.y = 0;
+ return rootAlias;
+ };
+ let wid = rootDoc[WidthSym]();
+ const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid);
+ const docs = await DocListCastAsync(Doc.GetProto(target).data);
+ if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target);
+ DocListCast(rootDoc.links).forEach(link => {
+ const other = LinkManager.getOppositeAnchor(link, rootDoc);
+ const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+ if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+ const alias = Doc.MakeAlias(otherdoc);
+ alias.x = wid;
+ alias.y = 0;
+ alias._lockedPosition = false;
+ wid += otherdoc[WidthSym]();
+ Doc.AddDocToList(Doc.GetProto(target), "data", alias);
+ }
+ });
+ LightboxView.SetLightboxDoc(target);
+ });
+ }
+ else DocumentLinksButton.LinkEditorDocView = this.props.View;
+ }));
+ }
+
@undoBatch
onLinkButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
@@ -78,36 +114,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
- } else if (!this.props.InMenu) {
- if (doubleTap) {
- const rootDoc = this.props.View.rootDoc;
- const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish";
- DocServer.GetRefField(docid).then(async docx => {
- const rootAlias = () => {
- const rootAlias = Doc.MakeAlias(rootDoc);
- rootAlias.x = rootAlias.y = 0;
- return rootAlias;
- };
- let wid = rootDoc[WidthSym]();
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid);
- const docs = await DocListCastAsync(Doc.GetProto(target).data);
- if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target);
- DocListCast(rootDoc.links).forEach(link => {
- const other = LinkManager.getOppositeAnchor(link, rootDoc);
- const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
- if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
- const alias = Doc.MakeAlias(otherdoc);
- alias.x = wid;
- alias.y = 0;
- alias._lockedPosition = false;
- wid += otherdoc[WidthSym]();
- Doc.AddDocToList(Doc.GetProto(target), "data", alias);
- }
- });
- LightboxView.SetLightboxDoc(target);
- });
- }
- else DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}));
}
@@ -120,17 +126,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
- } else {
+ } else { //if this LinkButton's Document is undefined
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
-
//action(() => Doc.BrushDoc(this.props.View.Document));
} else if (!this.props.InMenu) {
DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}
+
completeLink = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => {
if (doubleTap && !this.props.StartLink) {
@@ -141,7 +147,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
} else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
const sourceDoc = DocumentLinksButton.StartLink;
const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
- const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
+ const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "links"); //why is long drag here when this is used for completing links by clicking?
LinkManager.currentLink = linkDoc;
@@ -184,7 +190,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
} else if (startLink !== endLink) {
endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
- const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
+ const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "link", undefined, undefined, true);
LinkManager.currentLink = linkDoc;
@@ -192,7 +198,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
- const dashHyperlink = Utils.prepend("/doc/" + (startIsAnnotation ? endLink[Id] : startLink[Id]));
+ const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId,
(startIsAnnotation ? startLink : endLink)); // edit annotation to add a Dash hyperlink to the linked doc
}
@@ -242,45 +248,50 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return results;
}
+ /**
+ * gets the JSX of the link button (btn used to start/complete links) OR the link-view button (btn on bottom left of each linked node)
+ *
+ * todo:glr / anh seperate functionality such as onClick onPointerDown of link menu button
+ */
@computed get linkButtonInner() {
- const btnDim = this.props.InMenu ? "20px" : "30px";
+ const btnDim = "30px";
const link = <img style={{ width: "22px", height: "16px" }} src={`/assets/${"link.png"}`} />;
-
- return <div className="documentLinksButton-cont" ref={this._linkButton}
- style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}
- >
- <div className={"documentLinksButton"}
- onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
- style={{
- backgroundColor: this.props.InMenu ? "" : "#add8e6",
- color: this.props.InMenu ? "white" : "black",
- width: btnDim,
- height: btnDim,
- }} >
- {this.props.InMenu ?
- this.props.StartLink ?
- <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
- : link
- : Array.from(this.filteredLinks).length}
- </div>
- {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ?
- <div className={"documentLinksButton-endLink"}
+ const isActive = (DocumentLinksButton.StartLink === this.props.View.props.Document) && this.props.StartLink;
+ return (!this.props.InMenu ?
+ <div className="documentLinksButton-cont"
+ style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}
+ >
+ <div className={"documentLinksButton"}
+ onPointerDown={this.onLinkMenuOpen} onClick={this.onLinkClick}
style={{
- width: btnDim, height: btnDim,
- backgroundColor: DocumentLinksButton.StartLink ? "" : "grey",
- opacity: DocumentLinksButton.StartLink ? "" : "50%",
- border: DocumentLinksButton.StartLink ? "" : "none",
- cursor: DocumentLinksButton.StartLink ? "pointer" : "default"
- }}
- onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
- onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)} />
- : (null)
- }
- {DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ?
- <div className={"documentLinksButton-startLink"} onPointerDown={this.clearLinks} onClick={this.clearLinks} style={{ width: btnDim, height: btnDim }} />
- : (null)
- }
- </div >;
+ backgroundColor: Colors.LIGHT_BLUE,
+ color: Colors.BLACK,
+ width: btnDim,
+ height: btnDim,
+ }}>
+ {Array.from(this.filteredLinks).length}
+ </div>
+ </div>
+ :
+ <div className="documentLinksButton-menu" ref={this._linkButton}>
+ {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node
+ <div className={"documentLinksButton-endLink"}
+ onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
+ onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
+ </div>
+ : (null)
+ }
+ {
+ this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+ <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
+ </div>
+ :
+ (null)
+ }
+ </div>
+ );
}
render() {
@@ -290,6 +301,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const buttonTitle = "Tap to view links; double tap to open link collection";
const title = this.props.InMenu ? menuTitle : buttonTitle;
+ //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) :
this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ?
<Tooltip title={<div className="dash-tooltip">{title}</div>}>
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 8f86417d6..7f164ca48 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -147,7 +147,7 @@
.documentView-titleWrapper,
.documentView-titleWrapper-hover {
overflow: hidden;
- color: white;
+ color: $black;
transform-origin: top left;
top: 0;
width: 100%;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 60fa462ad..745d58656 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,7 +43,7 @@ import { DocumentLinksButton } from './DocumentLinksButton';
import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
import { LinkDocPreview } from "./LinkDocPreview";
-import { PresBox } from './PresBox';
+import { PresBox } from './trails/PresBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
import { ScriptingBox } from "./ScriptingBox";
@@ -64,7 +64,7 @@ export enum ViewAdjustment {
doNothing = 0
}
-export const ViewSpecPrefix = "_VIEW"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
+export const ViewSpecPrefix = "viewSpec"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
export interface DocFocusOptions {
originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
@@ -420,8 +420,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
focus = (anchor: Doc, options?: DocFocusOptions) => {
LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
- // copying over _VIEW fields immediately allows the view type to switch to create the right _componentView
- Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]));
+ // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
+ Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
+ this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
+ });
// after a timeout, the right _componentView should have been created, so call it to update its view spec values
setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
@@ -746,7 +748,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
}
}
@@ -754,16 +756,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
}
- !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
- cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
-
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
!Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
!Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
}
+
if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
@@ -838,7 +839,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
// need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews. All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here
- const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
+ const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
return filtered.map((link, i) =>
<div className="documentView-anchorCont" key={i + 1}>
<DocumentView {...this.props}
@@ -885,7 +886,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
- const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 });
+ const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
audioDoc.treeViewExpandedView = "layout";
const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
if (audioAnnos === undefined) {
@@ -971,7 +972,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
const highlightColor = (CurrentUserUtils.ActiveDashboard?.darkScheme ?
["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
- ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
+ ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
@@ -979,7 +980,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
- const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
+ const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
return <div className={DocumentView.ROOT_DIV} ref={this._mainCont}
onContextMenu={this.onContextMenu}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 86250c9d1..ebbc1138a 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -64,9 +64,9 @@ export class FieldView extends React.Component<FieldViewProps> {
// else if (field instaceof PresBox) {
// return <PresBox {...this.props} />;
// }
- else if (field instanceof VideoField) {
- return <VideoBox {...this.props} />;
- }
+ // else if (field instanceof VideoField) {
+ // return <VideoBox {...this.props} />;
+ // }
// else if (field instanceof AudioField) {
// return <AudioBox {...this.props} />;
//}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 6ae4b9726..0d415e238 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -14,6 +14,7 @@ import { DocComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import './FontIconBox.scss';
+import { Colors } from '../global/globalEnums';
const FontIconSchema = createSchema({
icon: "string",
});
@@ -47,7 +48,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
const icon = StrCast(this.dataDoc.icon, "user") as any;
const presSize = shape === 'round' ? 25 : 30;
const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`}
- style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />;
+ style={{ width: presSize, height: presSize, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})`, marginBottom: "5px" }} />;
const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu}
style={{ backgroundColor: backgroundColor, }}>
<div className="menuButton-wrap">
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index cfd43bb62..2c0106960 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -238,7 +238,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
let succeeded = true;
let data: ImageField | undefined;
try {
- data = new ImageField(Utils.prepend(accessPaths.agnostic.client));
+ data = new ImageField(accessPaths.agnostic.client);
} catch {
succeeded = false;
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
index d92823ccc..a8db5d360 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.scss
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -1,9 +1,13 @@
+@import "../global/globalCssVariables.scss";
+
.linkDescriptionPopup {
display: flex;
-
- border: 1px solid rgb(170, 26, 26);
-
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ border: 2px solid $medium-blue;
+ background-color: $white;
width: auto;
position: absolute;
@@ -11,17 +15,11 @@
z-index: 10000;
border-radius: 10px;
font-size: 12px;
- //white-space: nowrap;
-
- background-color: rgba(250, 250, 250, 0.95);
- padding-top: 9px;
- padding-bottom: 9px;
- padding-left: 9px;
- padding-right: 9px;
+ gap: 5px;
+ padding: 9px;
.linkDescriptionPopup-input {
float: left;
- background-color: rgba(250, 250, 250, 0.95);
color: rgb(100, 100, 100);
border: none;
min-width: 160px;
@@ -30,46 +28,29 @@
.linkDescriptionPopup-btn {
float: right;
-
justify-content: center;
vertical-align: middle;
-
.linkDescriptionPopup-btn-dismiss {
- background-color: white;
- color: black;
+ cursor: pointer;
display: inline;
- right: 0;
- border-radius: 10px;
- border: 1px solid black;
- padding: 3px;
- font-size: 9px;
- text-align: center;
- position: relative;
- margin-right: 4px;
- justify-content: center;
-
- &:hover{
- cursor: pointer;
- }
+ white-space: nowrap;
+ padding: 5px;
+ vertical-align: middle;
+ background-color: $close-red;
+ border-radius: 3px;
+ color: black;
}
.linkDescriptionPopup-btn-add {
- background-color: black;
- color: white;
+ cursor: pointer;
display: inline;
- right: 0;
- border-radius: 10px;
- border: 1px solid black;
- padding: 3px;
- font-size: 9px;
- text-align: center;
- position: relative;
- justify-content: center;
-
- &:hover{
- cursor: pointer;
- }
+ white-space: nowrap;
+ padding: 5px;
+ vertical-align: middle;
+ background-color: $light-blue;
+ border-radius: 3px;
+ color: black;
}
}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index b73fb10df..126a37eb8 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -72,14 +72,14 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
@computed get href() {
if (this.props.hrefs?.length) {
const href = this.props.hrefs[this._hrefInd];
- if (href.indexOf(Utils.prepend("/doc/")) !== 0) { // link to a web page URL -- try to show a preview
+ if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview
if (href.startsWith("https://en.wikipedia.org/wiki/")) {
wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
} else {
setTimeout(action(() => this._toolTipText = "url => " + href));
}
} else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
- const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0];
anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
if (anchor instanceof Doc && DocListCast(anchor.links).length) {
this._linkDoc = DocListCast(anchor.links)[0];
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8f61e252b..23236cf20 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,13 +3,13 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
import { documentSchema } from '../../../fields/documentSchemas';
import { makeInterface } from "../../../fields/Schema";
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast, BoolCast } from '../../../fields/Types';
import { PdfField } from "../../../fields/URLField";
import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, returnOne } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { KeyCodes } from '../../util/KeyCodes';
import { undoBatch } from '../../util/UndoManager';
@@ -31,6 +31,7 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
+ public static openSidebarWidth = 250;
private _searchString: string = "";
private _initialScrollTarget: Opt<Doc>;
private _pdfViewer: PDFViewer | undefined;
@@ -53,30 +54,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href));
else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action(pdf => this._pdf = pdf));
}
-
- const backup = "oldPath";
- const href = this.pdfUrl?.url.href;
- if (href) {
- const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g;
- const matches = pathCorrectionTest.exec(href);
- // console.log("\nHere's the { url } being fed into the outer regex:");
- // console.log(href);
- // console.log("And here's the 'properPath' build from the captured filename:\n");
- if (matches !== null && href.startsWith(window.location.origin)) {
- const properPath = Utils.prepend(`/files/pdfs/${matches[0]}`);
- //console.log(properPath);
- if (!properPath.includes(href)) {
- console.log(`The two (url and proper path) were not equal`);
- const proto = Doc.GetProto(this.props.Document);
- proto[this.props.fieldKey] = new PdfField(properPath);
- proto[backup] = href;
- } else {
- //console.log(`The two (url and proper path) were equal`);
- }
- } else {
- console.log("Outer matches was null!");
- }
- }
}
componentWillUnmount() { this._selectReactionDisposer?.(); }
@@ -90,6 +67,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
scrollFocus = (doc: Doc, smooth: boolean) => {
+ if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+ this.toggleSidebar(!smooth);
+ }
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
this._initialScrollTarget = doc;
return this._pdfViewer?.scrollFocus(doc, smooth);
@@ -165,15 +145,24 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
}
return false;
- }, emptyFunction, this.toggleSidebar);
+ }, emptyFunction, () => this.toggleSidebar());
}
- toggleSidebar = action(() => {
+ @observable _previewNativeWidth: Opt<number> = undefined;
+ @observable _previewWidth: Opt<number> = undefined;
+ toggleSidebar = action((preview: boolean = false) => {
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
+ const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ if (preview) {
+ this._previewNativeWidth = nativeWidth * ratio;
+ this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this._showSidebar = true;
+ }
+ else {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
});
settingsPanel() {
const pageBtns = <>
@@ -226,7 +215,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</button>
</div>;
}
- sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+ sidebarWidth = () => !this.SidebarShown ? 0 :
+ this._previewWidth ? PDFBox.openSidebarWidth :
+ (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
@@ -247,38 +238,55 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ contentScaling = () => {
+ return 1;
+ }
@computed get renderPdfView() {
TraceMobx();
+ const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+ const scale = previewScale * (this.props.scaling?.() || 1);
return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}
style={{
height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ?
NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined
}}>
<div className="pdfBox-background" />
- <PDFViewer {...this.props}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- pdf={this._pdf!}
- url={this.pdfUrl!.url.pathname}
- isContentActive={this.isContentActive}
- anchorMenuClick={this.anchorMenuClick}
- loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
- setPdfViewer={this.setPdfViewer}
- addDocument={this.addDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.removeDocument}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- startupLive={true}
- ContentScaling={this.props.scaling}
- sidebarWidth={this.sidebarWidth}
- />
+ <div style={{
+ width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
+ height: `${100 / scale}%`,
+ transform: `scale(${scale})`,
+ position: "absolute",
+ transformOrigin: "top left",
+ top: 0
+ }}>
+ <PDFViewer {...this.props}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ pdf={this._pdf!}
+ url={this.pdfUrl!.url.pathname}
+ isContentActive={this.isContentActive}
+ anchorMenuClick={this.anchorMenuClick}
+ loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
+ setPdfViewer={this.setPdfViewer}
+ addDocument={this.addDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.removeDocument}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ startupLive={true}
+ ContentScaling={returnOne}
+ />
+ </div>
<SidebarAnnos ref={this._sidebarRef}
{...this.props}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 700f8a7d3..68ab3193b 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,11 +1,11 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { Canvas } from '@react-three/fiber';
-import { action, computed, observable, reaction } from "mobx";
+import { action, computed, observable, reaction, trace, runInAction } from "mobx";
import { observer } from "mobx-react";
// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
import { DateField } from "../../../fields/DateField";
-import { Doc, WidthSym } from "../../../fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
@@ -218,16 +218,15 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
// }
return (null);
}
- toggleRecording = action(async () => {
- this._screenCapture = !this._screenCapture;
- if (this._screenCapture) {
+ toggleRecording = async () => {
+ if (!this._screenCapture) {
this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
const aud_chunks: any = [];
this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data);
this._audioRec.onstop = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(aud_chunks);
if (!(result instanceof Error)) {
- this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client));
+ this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
@@ -244,23 +243,29 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey);
this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined;
this.layoutDoc._fitWidth = undefined;
- this.dataDoc[this.props.fieldKey] = new VideoField(Utils.prepend(result.accessPaths.agnostic.client));
+ this.dataDoc[this.props.fieldKey] = new VideoField(result.accessPaths.agnostic.client);
} else alert("video conversion failed");
};
this._audioRec.start();
this._videoRec.start();
- this.dataDoc.mediaState = "recording";
+ runInAction(() => {
+ this._screenCapture = true;
+ this.dataDoc.mediaState = "recording";
+ });
DocUtils.ActiveRecordings.push(this);
} else {
this._audioRec?.stop();
this._videoRec?.stop();
- this.dataDoc.mediaState = "paused";
+ runInAction(() => {
+ this._screenCapture = false;
+ this.dataDoc.mediaState = "paused";
+ });
const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
CaptureManager.Instance.open(this.rootDoc);
}
- });
+ };
setupDictation = () => {
if (this.dataDoc[this.fieldKey + "-dictation"]) return;
@@ -275,7 +280,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.dataDoc[this.fieldKey + "-dictation"] = dictationText;
}
contentFunc = () => [this.threed, this.content];
- videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth();
+ videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], this.layoutDoc[WidthSym]()) * this.props.PanelWidth();
formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
render() {
TraceMobx();
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index fc08a2302..ce45c01e6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -75,10 +75,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined) || this.rootDoc;
}
- choosePath(url: string) {
- return url.indexOf(window.location.origin) === -1 ? Utils.CorsProxy(url) : url;
- }
-
videoLoad = () => {
const aspect = this.player!.videoWidth / this.player!.videoHeight;
Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
@@ -182,8 +178,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
}
- private createRealSummaryLink = (relative: string, downX?: number, downY?: number) => {
- const url = this.choosePath(Utils.prepend(relative));
+ private createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => {
+ const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath;
const width = this.layoutDoc._width || 1;
const height = this.layoutDoc._height || 0;
const imageSummary = Docs.Create.ImageDocument(url, {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 88e38712a..f3a4a46de 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -43,6 +43,7 @@ const WebDocument = makeInterface(documentSchema);
@observer
export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
+ public static openSidebarWidth = 250;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -51,6 +52,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _keyInput = React.createRef<HTMLInputElement>();
private _initialScroll: Opt<number>;
private _sidebarRef = React.createRef<SidebarAnnos>();
+ @observable private _urlHash: string = "";
@observable private _scrollTimer: any;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private _marqueeing: number[] | undefined;
@@ -67,7 +69,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
constructor(props: any) {
super(props);
- if (this.webField) {
+ if (true) {// his.webField) {
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
@@ -81,9 +83,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
runInAction(() => {
this._url = this.webField?.toString() || "";
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
+ this._urlHash = WebBox.urlHash(this._url) + "";
+ this._annotationKey = this._urlHash + "-annotations";
// bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created)
- this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-annotations-"+urlHash(this["${this.fieldKey}"]?.url?.toString()))`);
+ this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`);
+ this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`);
});
this._disposers.selection = reaction(() => this.props.isSelected(),
@@ -160,9 +164,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
scrollFocus = (doc: Doc, smooth: boolean) => {
+ if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
+ if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+ this.toggleSidebar(!smooth);
+ }
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
- const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1);
if (scrollTo !== undefined) {
const focusSpeed = smooth ? 500 : 0;
this._initialScroll !== undefined && (this._initialScroll = scrollTo);
@@ -177,12 +186,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
getAnchor = () => {
const anchor =
AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
- Docs.Create.TextanchorDocument({
+ Docs.Create.WebanchorDocument(this._url, {
title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
annotationOn: this.rootDoc,
- y: NumCast(this.layoutDoc._scrollTop),
+ y: NumCast(this.layoutDoc._scrollTop)
});
- this.addDocument(anchor);
+ this.addDocumentWrapper(anchor);
return anchor;
}
@@ -294,9 +303,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
if (future.length) {
- history.push(this._url);
+ this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
+ this._urlHash = WebBox.urlHash(this._url) + "";
+ this._annotationKey = this._urlHash + "-annotations";
return true;
}
return false;
@@ -308,9 +318,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
if (history.length) {
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
- else future.push(this._url);
+ else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
+ this._urlHash = WebBox.urlHash(this._url) + "";
+ this._annotationKey = this._urlHash + "-annotations";
return true;
}
return false;
@@ -321,25 +332,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
@action
- submitURL = (newUrl?: string) => {
+ submitURL = (newUrl?: string, preview?: boolean) => {
if (!newUrl) return;
if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
try {
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"));
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"));
const url = this.webField?.toString();
- if (url) {
+ if (url && !preview) {
if (history === undefined) {
this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
} else {
- history.push(url);
+ this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, url]);
}
this.layoutDoc._scrollTop = 0;
future && (future.length = 0);
}
this._url = newUrl;
- this._annotationKey = "annotations-" + WebBox.urlHash(this._url);
- this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
+ this._urlHash = WebBox.urlHash(this._url) + "";
+ this._annotationKey = this._urlHash + "-annotations";
+ if (!preview) this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
} catch (e) {
console.log("WebBox URL error:" + this._url);
}
@@ -417,7 +429,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (field instanceof HtmlField) {
view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
+ const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._url) : this._url;
view = <iframe className="webBox-iframe" enable-annotation={"true"}
style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
@@ -432,9 +444,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return view;
}
+ addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => doc.webUrl = this._url);
+ return this.addDocument(doc, annotationKey);
+ }
+
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
- return this.addDocument(doc, sidebarKey);
+ return this.addDocumentWrapper(doc, sidebarKey);
}
sidebarBtnDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
@@ -448,17 +465,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
}
return false;
- }, emptyFunction, this.toggleSidebar);
+ }, emptyFunction, () => this.toggleSidebar());
}
- toggleSidebar = action(() => {
+ @observable _previewNativeWidth: Opt<number> = undefined;
+ @observable _previewWidth: Opt<number> = undefined;
+ toggleSidebar = action((preview: boolean = false) => {
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth;
+ const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ if (preview) {
+ this._previewNativeWidth = nativeWidth * ratio;
+ this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this._showSidebar = true;
+ }
+ else {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth;
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
});
- sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
+ sidebarWidth = () => !this.SidebarShown ? 0 :
+ this._previewWidth ? WebBox.openSidebarWidth :
+ (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() /
+ NumCast(this.layoutDoc.nativeWidth)
@computed get content() {
return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
@@ -474,7 +503,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
}
</div>;
+
}
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
@@ -483,29 +515,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
render() {
- const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
- const scale = this.props.scaling?.() || 1;
+ const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined;
+ const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+ const scale = previewScale * (this.props.scaling?.() || 1);
return (
<div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.isContentActive() ? "all" : this.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
- <div className={`webBox-container`}
- style={{ pointerEvents: inactiveLayer ? "none" : undefined }}
- onContextMenu={this.specificContextMenu}>
+ <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
<base target="_blank" />
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
- width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale}px)`,
+ width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
height: `${100 / scale}%`,
transform: `scale(${scale})`,
- pointerEvents: inactiveLayer ? "none" : undefined
+ pointerEvents
}}
onWheel={e => { e.stopPropagation(); e.preventDefault(); }} // block wheel events from propagating since they're handled by the iframe
onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
onPointerDown={this.onMarqueeDown}
>
- <div className={"webBox-innerContent"} style={{
- height: NumCast(this.scrollHeight, 50),
- pointerEvents: inactiveLayer ? "none" : undefined
- }}>
+ <div className={"webBox-innerContent"} style={{ height: NumCast(this.scrollHeight, 50), pointerEvents }}>
{this.content}
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
@@ -538,7 +566,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
anchorMenuClick={this.anchorMenuClick}
scrollTop={0}
down={this._marqueeing} scaling={returnOne}
- addDocument={this.addDocument}
+ addDocument={this.addDocumentWrapper}
docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
savedAnnotations={this._savedAnnotations}
@@ -547,10 +575,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</div >
<SidebarAnnos ref={this._sidebarRef}
{...this.props}
- fieldKey={this.annotationKey}
+ fieldKey={this.fieldKey + "-" + this._urlHash}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6dd63fb47..9bba15b28 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { ReplaceStep } from 'prosemirror-transform';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
+import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -120,7 +120,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public get EditorView() { return this._editorView; }
public get SidebarKey() { return this.fieldKey + "-sidebar"; }
- @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
+ @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
@computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
@computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; }
@computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
@@ -253,7 +253,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) {
const accumTags = [] as string[];
state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
@@ -370,7 +370,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
const anchor = Docs.Create.TextanchorDocument();
const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
- const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }];
const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location });
tr = tr.addMark(flattened[i].from, flattened[i].to, link);
});
@@ -431,6 +431,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.ProseRef = ele;
this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
}
@undoBatch
@@ -543,11 +544,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+
@action
- toggleSidebar = () => {
+ toggleSidebar = (preview: boolean = false) => {
const prevWidth = this.sidebarWidth();
- this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
- this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ if (preview) this._showSidebar = true;
+ else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+
+ this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
}
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
@@ -704,7 +710,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) });
- const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+ const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
@@ -725,6 +731,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+ if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) {
+ this.toggleSidebar(!smooth);
+ }
const textAnchorId = textAnchor[Id];
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
@@ -787,7 +796,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
componentDidMount() {
- this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+ !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
@@ -1041,7 +1050,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
+ const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
@@ -1393,6 +1402,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._rules!.EnteringStyle = false;
}
e.stopPropagation();
+ for (var i = state.selection.from; i < state.selection.to; i++) {
+ const node = state.doc.resolve(i);
+ if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
+ mark.attrs.userid !== Doc.CurrentUserEmail) &&
+ [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+ e.preventDefault();
+ }
+ }
switch (e.key) {
case "Escape":
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
@@ -1422,16 +1439,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- tryUpdateScrollHeight() {
+ tryUpdateScrollHeight = () => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const proseHeight = !this.ProseRef ? 0 : Array.from(this.ProseRef.children[0].children).reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
- const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
- if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
- setScrollHeight();
- } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
+ const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
+ if (children) {
+ const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+ if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
+ if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
+ setScrollHeight();
+ } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
+ }
}
}
}
@@ -1467,10 +1487,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return ComponentTag === CollectionStackingView ?
<SidebarAnnos ref={this._sidebarRef}
{...this.props}
- fieldKey={this.annotationKey}
+ fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
PanelWidth={this.sidebarWidth}
setHeight={this.setSidebarHeight}
sidebarAddDocument={this.sidebarAddDocument}
@@ -1568,7 +1590,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
+ {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
{(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle}
{!this.layoutDoc._showAudio ? (null) : this.audioHandle}
</div>
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index d5c77786c..1f78b2204 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,13 +7,14 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "../../../util/SelectionManager";
import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast } from "../../../../fields/Doc";
+import { Doc, DataSym, DocListCast, AclAugment } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
import { Docs } from "../../../documents/Documents";
import { Utils } from "../../../../Utils";
import { listSpec } from "../../../../fields/Schema";
import { List } from "../../../../fields/List";
+import { GetEffectiveAcl } from "../../../../fields/util";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -70,25 +71,39 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
};
+ const canEdit = (state: any) => {
+ for (var i = state.selection.from; i < state.selection.to; i++) {
+ const node = state.doc.resolve(i);
+ if (node?.marks?.().some((mark: any) => mark.type === schema.marks.user_mark &&
+ mark.attrs.userid !== Doc.CurrentUserEmail) &&
+ GetEffectiveAcl(props.Document) === AclAugment) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+
//History commands
bind("Mod-z", undo);
bind("Shift-Mod-z", redo);
!mac && bind("Mod-y", redo);
//Commands to modify Mark
- bind("Mod-b", toggleMark(schema.marks.strong));
- bind("Mod-B", toggleMark(schema.marks.strong));
+ bind("Mod-b", toggleEditableMark(schema.marks.strong));
+ bind("Mod-B", toggleEditableMark(schema.marks.strong));
- bind("Mod-e", toggleMark(schema.marks.em));
- bind("Mod-E", toggleMark(schema.marks.em));
+ bind("Mod-e", toggleEditableMark(schema.marks.em));
+ bind("Mod-E", toggleEditableMark(schema.marks.em));
- bind("Mod-*", toggleMark(schema.marks.code));
+ bind("Mod-*", toggleEditableMark(schema.marks.code));
- bind("Mod-u", toggleMark(schema.marks.underline));
- bind("Mod-U", toggleMark(schema.marks.underline));
+ bind("Mod-u", toggleEditableMark(schema.marks.underline));
+ bind("Mod-U", toggleEditableMark(schema.marks.underline));
//Commands for lists
- bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
+ bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch as any));
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
/// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
@@ -96,6 +111,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (!props.LayoutTemplateString) return addTextBox(false, true);
return true;
}
+ if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -121,6 +137,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
/// bcz; Argh!! replace with a onShiftTab prop conditionally handles Tab);
if (props.Document._singleLine) return true;
+ if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
@@ -140,24 +157,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
//Commands to modify BlockType
- bind("Ctrl->", wrapIn(schema.nodes.blockquote));
- bind("Alt-\\", setBlockType(schema.nodes.paragraph));
- bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+ bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state, dispatch as any)));
+ bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch as any));
+ bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch as any));
- bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })));
- });
+ bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+ bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch as any));
}
//Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
- return true;
- });
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
//Command to unselect all
bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -173,13 +185,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
//Command to create a text document to the right of the selected textbox
- bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => addTextBox(false, true));
+ bind("Alt-Enter", () => addTextBox(false, true));
//Command to create a text document to the bottom of the selected textbox
- bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => addTextBox(true, true));
+ bind("Ctrl-Enter", () => addTextBox(true, true));
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ if (!canEdit(state)) return true;
+
if (!deleteSelection(state, (tx: Transaction<Schema<any, any>>) => {
dispatch(updateBullets(tx, schema));
})) {
@@ -200,6 +214,9 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//command to break line
bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
if (addTextBox(true, false)) return true;
+
+ if (!canEdit(state)) return true;
+
const trange = state.selection.$from.blockRange(state.selection.$to);
const path = (state.selection.$from as any).path;
const depth = trange ? liftTarget(trange) : undefined;
@@ -238,18 +255,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a blank space
bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
- bind("Alt-ArrowUp", joinUp);
- bind("Alt-ArrowDown", joinDown);
- bind("Mod-BracketLeft", lift);
+ bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
+ bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
+ bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+ canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
return true;
}
return false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 59b2d3753..82ad2b7db 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -821,8 +821,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (link) {
const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ if (href.indexOf(Doc.localServerPath()) === 0) {
+ const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0];
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
@@ -852,6 +852,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
+ console.log((this.view as any)?.TextView);
}
@undoBatch
@@ -863,8 +864,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const allAnchors = linkAnchor.attrs.allAnchors.slice();
this.TextView.RemoveAnchorFromSelection(allAnchors);
// bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
- allAnchors.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
- const anchorId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => {
+ const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0];
anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
});
}
@@ -963,7 +964,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.createHighlighterButton(),
this.createLinkButton(),
this.createBrushButton(),
- <div className="richTextMenu-divider" key="divider 2" />,
+ <div className="collectionMenu-divider" key="divider 2" />,
this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
@@ -976,7 +977,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const row2 = <div className="antimodeMenu-row row-2" key="row2">
{this.collapsed ? this.getDragger() : (null)}
<div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
- <div className="richTextMenu-divider" key="divider 3" />
+ <div className="collectionMenu-divider" key="divider 3" />
{[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
this.activeFontSize = val;
SelectionManager.Views().map(dv => dv.props.Document._fontSize = val);
@@ -985,12 +986,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeFontFamily = val;
SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val);
})),
- <div className="richTextMenu-divider" key="divider 4" />,
+ <div className="collectionMenu-divider" key="divider 4" />,
this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
- this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
- <div className="richTextMenu-divider" key="divider 5" />,]}
+ this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
+ ]}
</div>
{/* <div key="collapser">
{<div key="collapser">
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 5d1c5f4eb..06932d145 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import "../../global/globalCssVariables";
.presBox-cont {
cursor: auto;
@@ -889,7 +889,7 @@
height: 13;
font-size: 12;
display: flex;
- background-color: #white;
+ background-color: $white;
}
.subtitle {
@@ -926,7 +926,7 @@
.presBox-buttons {
position: relative;
width: 100%;
- background: gray;
+ background: $medium-gray;
min-height: 35px;
padding-top: 5px;
padding-bottom: 5px;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f3fb6ff17..5cb9866f8 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -5,67 +5,33 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
-import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../fields/Doc";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { InkTool } from "../../../fields/InkField";
-import { List } from "../../../fields/List";
-import { PrefetchProxy } from "../../../fields/Proxy";
-import { listSpec, makeInterface } from "../../../fields/Schema";
-import { ScriptField } from "../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { returnFalse, returnOne, returnTrue, emptyFunction } from '../../../Utils';
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { Scripting } from "../../util/Scripting";
-import { SelectionManager } from "../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { TabDocView } from "../collections/TabDocView";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { FieldView, FieldViewProps } from './FieldView';
+import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../../fields/Doc";
+import { documentSchema } from "../../../../fields/documentSchemas";
+import { InkTool } from "../../../../fields/InkField";
+import { List } from "../../../../fields/List";
+import { PrefetchProxy } from "../../../../fields/Proxy";
+import { listSpec, makeInterface } from "../../../../fields/Schema";
+import { ScriptField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { emptyFunction, returnFalse, returnOne, returnTrue } from '../../../../Utils';
+import { Docs } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { Scripting } from "../../../util/Scripting";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { CollectionDockingView } from "../../collections/CollectionDockingView";
+import { CollectionView, CollectionViewType } from "../../collections/CollectionView";
+import { TabDocView } from "../../collections/TabDocView";
+import { ViewBoxBaseComponent } from "../../DocComponent";
+import { Colors } from "../../global/globalEnums";
+import { LightboxView } from "../../LightboxView";
+import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentView";
+import { FieldView, FieldViewProps } from '../FieldView';
import "./PresBox.scss";
import Color = require("color");
-import { LightboxView } from "../LightboxView";
-
-export enum PresMovement {
- Zoom = "zoom",
- Pan = "pan",
- Jump = "jump",
- None = "none",
-}
-
-export enum PresEffect {
- Zoom = "Zoom",
- Lightspeed = "Lightspeed",
- Fade = "Fade in",
- Flip = "Flip",
- Rotate = "Rotate",
- Bounce = "Bounce",
- Roll = "Roll",
- None = "None",
- Left = "left",
- Right = "right",
- Center = "center",
- Top = "top",
- Bottom = "bottom"
-}
-
-enum PresStatus {
- Autoplay = "auto",
- Manual = "manual",
- Edit = "edit"
-}
-
-export enum PresColor {
- LightBlue = "#AEDDF8",
- DarkBlue = "#5B9FDD",
- LightBackground = "#ececec",
- SlideBackground = "#d5dce2",
-}
+import { PresEffect, PresStatus, PresMovement } from "./PresEnums";
export class PinProps {
audioRange?: boolean;
@@ -1198,9 +1164,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
{this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"}
</div>
:
- <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}>
+ <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
{this.setMovementName(activeItem.presMovement, activeItem)}
- <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} />
+ <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}>
<div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div>
<div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div>
@@ -1245,7 +1211,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-doubleButton">
{isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>}
{isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>}
- <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip>
</div>
{(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <>
<div className="ribbon-doubleButton" >
@@ -1280,9 +1246,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
{isPresCollection ? (null) : <div className="ribbon-box">
Effects
- <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}>
+ <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
{effect}
- <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} />
+ <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}>
<div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
<div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
@@ -1299,11 +1265,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
</div>
<div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${PresColor.LightBlue}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
</div>
</div>}
<div className="ribbon-final-box">
@@ -1356,7 +1322,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<>
{this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)}
<div className="ribbon-doubleButton">
- <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? PresColor.LightBlue : "" }}
+ <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : "" }}
onClick={() => {
activeItem.presPinView = !activeItem.presPinView;
targetDoc.presPinView = activeItem.presPinView;
@@ -1496,7 +1462,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="slider-text" style={{ fontWeight: 500 }}>
Start time (s)
</div>
- <div id={"startTime"} className="slider-number" style={{ backgroundColor: PresColor.LightBackground }}>
+ <div id={"startTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
<input className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presStartTime)}
@@ -1508,7 +1474,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="slider-text" style={{ fontWeight: 500 }}>
Duration (s)
</div>
- <div className="slider-number" style={{ backgroundColor: PresColor.LightBlue }}>
+ <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}>
{Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
</div>
</div>
@@ -1516,7 +1482,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="slider-text" style={{ fontWeight: 500 }}>
End time (s)
</div>
- <div id={"endTime"} className="slider-number" style={{ backgroundColor: PresColor.LightBackground }}>
+ <div id={"endTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
<input className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presEndTime)}
@@ -1534,16 +1500,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this._batch = UndoManager.StartBatch("presEndTime");
const endBlock = document.getElementById("endTime");
if (endBlock) {
- endBlock.style.color = PresColor.LightBackground;
- endBlock.style.backgroundColor = PresColor.DarkBlue;
+ endBlock.style.color = Colors.LIGHT_GRAY;
+ endBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
}
}}
onPointerUp={() => {
this._batch?.end();
const endBlock = document.getElementById("endTime");
if (endBlock) {
- endBlock.style.color = "black";
- endBlock.style.backgroundColor = PresColor.LightBackground;
+ endBlock.style.color = Colors.BLACK;
+ endBlock.style.backgroundColor = Colors.LIGHT_GRAY;
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1558,16 +1524,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this._batch = UndoManager.StartBatch("presStartTime");
const startBlock = document.getElementById("startTime");
if (startBlock) {
- startBlock.style.color = PresColor.LightBackground;
- startBlock.style.backgroundColor = PresColor.DarkBlue;
+ startBlock.style.color = Colors.LIGHT_GRAY;
+ startBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
}
}}
onPointerUp={() => {
this._batch?.end();
const startBlock = document.getElementById("startTime");
if (startBlock) {
- startBlock.style.color = "black";
- startBlock.style.backgroundColor = PresColor.LightBackground;
+ startBlock.style.color = Colors.BLACK;
+ startBlock.style.backgroundColor = Colors.LIGHT_GRAY;
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1651,15 +1617,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div>
<div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div className="layout-container" style={{ height: 'max-content' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} />
+ <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}>
+ <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}>
<div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}>
+ <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}>
<div className="title" style={{ alignSelf: 'center' }}>Title</div>
<div className="content">Text goes here</div>
</div>
@@ -1691,26 +1657,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-box">
Choose type:
<div className="ribbon-doubleButton">
- <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : PresColor.LightBlue }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div>
- <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? PresColor.LightBlue : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div>
+ <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : Colors.LIGHT_BLUE }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div>
+ <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div>
</div>
</div>
<div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}>
Preset layouts:
<div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'blank')} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'title')}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'blank')} />
+ <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'title')}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'header')}>
+ <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'header')}>
<div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'content')}>
+ <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'content')}>
<div className="title" style={{ alignSelf: 'center' }}>Title</div>
<div className="content">Text goes here</div>
</div>
- <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'twoColumns')}>
+ <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'twoColumns')}>
<div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div>
<div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div>
<div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div>
@@ -1869,8 +1835,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-box">
{this.stringType} selected
<div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeChild}>Contents</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? PresColor.LightBlue : "" }} onClick={this.editProgressivize}>Edit</div>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeChild}>Contents</div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editProgressivize}>Edit</div>
</div>
<div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}>
<div className="presBox-subheading">Active text color</div>
@@ -1885,12 +1851,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
{this.viewedColorPicker}
<div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeZoom}>Zoom</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? PresColor.LightBlue : "" }} onClick={this.editZoomProgressivize}>Edit</div>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeZoom}>Zoom</div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editZoomProgressivize}>Edit</div>
</div>
<div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeScroll}>Scroll</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.editScrollProgressivize}>Edit</div>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeScroll}>Scroll</div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editScrollProgressivize}>Edit</div>
</div>
</div>
<div className="ribbon-final-box">
@@ -1900,7 +1866,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}>
<FontAwesomeIcon icon={"caret-left"} size={"lg"} />
</div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? PresColor.DarkBlue : PresColor.LightBlue }}
+ <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }}
onClick={action(() => targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing)} >
{NumCast(targetDoc._currentFrame)}
</div>
@@ -1914,7 +1880,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
{this.frameListHeader}
{this.frameList}
</div>
- <div className="ribbon-toggle" style={{ height: 20, backgroundColor: PresColor.LightBlue }} onClick={() => console.log(" TODO: play frames")}>Play</div>
+ <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(" TODO: play frames")}>Play</div>
</div>
</div>
</div>
@@ -2130,7 +2096,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>);
}
tags.push(
- <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? PresColor.LightBlue : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
+ <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
<div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div>
<div className="progressivizeButton-frame">{doc.appearFrame}</div>
<div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div>
@@ -2213,6 +2179,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation);
+ const activeColor = Colors.LIGHT_BLUE;
+ const inactiveColor = Colors.WHITE;
return (mode === CollectionViewType.Carousel3D) ? (null) : (
<div id="toolbarContainer" className={'presBox-toolbar'}>
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
@@ -2220,7 +2188,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
</div></Tooltip> */}
<Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}>
- <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? PresColor.DarkBlue : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
+ <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
<FontAwesomeIcon icon={"exchange-alt"} />
</div>
</Tooltip>
@@ -2229,7 +2197,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="toolbar-divider" />
{/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}>
<div className={"toolbar-button"}
- style={{ color: this._expandBoolean ? PresColors.DarkBlue : 'white' }}
+ style={{ color: this._expandBoolean ? Colors.MEDIUM_BLUE : 'white' }}
onClick={this.toggleExpandMode}>
<FontAwesomeIcon icon={"eye"} />
</div>
@@ -2237,12 +2205,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="toolbar-divider" /> */}
<Tooltip title={<><div className="dash-tooltip">{presKeyEvents ? "Keys are active" : "Keys are not active - click anywhere on the presentation trail to activate keys"}</div></>}>
<div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
- <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? PresColor.DarkBlue : 'white' }} />
+ <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? activeColor : inactiveColor }} />
</div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}>
<div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
- <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? PresColor.DarkBlue : 'white' }} />
+ <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
@@ -2379,7 +2347,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
// Case 1: There are still other frames and should go through all frames before going to next slide
return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? PresColor.DarkBlue : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
<div className="presPanel-divider"></div>
<div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
<Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? "pause" : "play"} /></div></Tooltip>
@@ -2418,8 +2386,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ?
<div className="miniPres">
- <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + PresColor.DarkBlue : undefined }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? PresColor.DarkBlue : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
<div className="presPanel-divider"></div>
<div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
<Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 1ad4b820e..1ad4b820e 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index f15d51764..5e713c3cf 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -2,27 +2,29 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, Opt } from "../../../fields/Doc";
-import { documentSchema } from '../../../fields/documentSchemas';
-import { Id } from "../../../fields/FieldSymbols";
-import { createSchema, makeInterface } from '../../../fields/Schema';
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath, returnEmptyDoclist } from "../../../Utils";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { EditableView } from "../EditableView";
-import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import { PresBox, PresColor, PresMovement } from "../nodes/PresBox";
-import { StyleProp } from "../StyleProvider";
+import { DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from "../../../../fields/FieldSymbols";
+import { createSchema, makeInterface } from '../../../../fields/Schema';
+import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath, returnEmptyDoclist } from "../../../../Utils";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DragManager } from "../../../util/DragManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { EditableView } from "../../EditableView";
+import { DocumentView, DocumentViewProps } from "../../nodes/DocumentView";
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { PresBox } from "./PresBox";
+import { Colors } from "../../global/globalEnums";
+import { StyleProp } from "../../StyleProvider";
import "./PresElementBox.scss";
import React = require("react");
-import { DocUtils } from "../../documents/Documents";
+import { DocUtils } from "../../../documents/Documents";
+import { PresMovement } from "./PresEnums";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -210,11 +212,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
const height = slide.clientHeight;
const halfLine = height / 2;
if (y <= halfLine) {
- slide.style.borderTop = "solid 2px #5B9FDD";
+ slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
slide.style.borderBottom = "0px";
} else if (y > halfLine) {
slide.style.borderTop = "0px";
- slide.style.borderBottom = "solid 2px #5B9FDD";
+ slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
}
}
document.removeEventListener("pointermove", this.onPointerMove);
@@ -292,7 +294,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
const miniView: boolean = this.toolbarWidth <= 110;
const presBox: Doc = this.presBox; //presBox
const presBoxColor: string = StrCast(presBox._backgroundColor);
- const presColorBool: boolean = presBoxColor ? (presBoxColor !== "white" && presBoxColor !== "transparent") : false;
+ const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false;
const targetDoc: Doc = this.targetDoc;
const activeItem: Doc = this.rootDoc;
return (
@@ -300,7 +302,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
key={this.props.Document[Id] + this.indexInPres}
ref={this._itemRef}
style={{
- backgroundColor: presColorBool ? isSelected ? "rgba(250,250,250,0.3)" : "transparent" : isSelected ? "#AEDDF8" : "transparent",
+ backgroundColor: presColorBool ? isSelected ? "rgba(250,250,250,0.3)" : "transparent" : isSelected ? Colors.LIGHT_BLUE : "transparent",
opacity: this._dragging ? 0.3 : 1
}}
onClick={e => {
@@ -356,7 +358,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
style={{
zIndex: 1000 - this.indexInPres,
fontWeight: 700,
- backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : PresColor.DarkBlue : undefined,
+ backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined,
height: activeItem.groupWithUp ? 53 : 18,
transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined
}}>
diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts
new file mode 100644
index 000000000..93ab323fb
--- /dev/null
+++ b/src/client/views/nodes/trails/PresEnums.ts
@@ -0,0 +1,28 @@
+export enum PresMovement {
+ Zoom = "zoom",
+ Pan = "pan",
+ Jump = "jump",
+ None = "none",
+}
+
+export enum PresEffect {
+ Zoom = "Zoom",
+ Lightspeed = "Lightspeed",
+ Fade = "Fade in",
+ Flip = "Flip",
+ Rotate = "Rotate",
+ Bounce = "Bounce",
+ Roll = "Roll",
+ None = "None",
+ Left = "left",
+ Right = "right",
+ Center = "center",
+ Top = "top",
+ Bottom = "bottom"
+}
+
+export enum PresStatus {
+ Autoplay = "auto",
+ Manual = "manual",
+ Edit = "edit"
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
new file mode 100644
index 000000000..8f3f7b03a
--- /dev/null
+++ b/src/client/views/nodes/trails/index.ts
@@ -0,0 +1,3 @@
+export * from "./PresBox";
+export * from "./PresElementBox";
+export * from "./PresEnums"; \ No newline at end of file
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index c24c4eaaf..55816ed52 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -10,6 +10,7 @@ import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
import "./AnchorMenu.scss";
import { SelectionManager } from "../../util/SelectionManager";
+import { LinkPopup } from "../linking/LinkPopup";
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -38,6 +39,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
@observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
+ @observable private _showLinkPopup: boolean = false;
@observable public _colorBtn = false;
@observable public Highlighting: boolean = false;
@@ -80,6 +82,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
+ @action
+ toggleLinkPopup = (e: React.MouseEvent) => {
+ //ignore the potential null type error because this method cannot be called unless the user selects text and clicks the link button
+ //change popup visibility field to visible
+ this._showLinkPopup = !this._showLinkPopup;
+ }
+
@computed get highlighter() {
const button =
<button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
@@ -136,6 +145,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<FontAwesomeIcon icon="comment-alt" size="lg" />
</button>
</Tooltip>,
+
+ //NOTE: link popup is currently incomplete
+ // <Tooltip key="link" title={<div className="dash-tooltip">{"Link selected text to document or URL"}</div>}>
+ // <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
+ // <FontAwesomeIcon icon="link" size="lg" />
+ // </button>
+ // </Tooltip>,
+ // <LinkPopup showPopup={this._showLinkPopup} />
] : [
<Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
<button className="antimodeMenu-button" onPointerDown={this.Delete}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4a50dccf3..e7911e8f8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -46,7 +46,6 @@ interface IViewerProps extends FieldViewProps {
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
ContentScaling?: () => number;
- sidebarWidth: () => number;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
}
@@ -184,7 +183,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont && this._pdfViewer) {
- const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
if (scrollTo !== undefined) {
focusSpeed = 500;
@@ -549,7 +549,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
style={{
overflowX: this._zoomed !== 1 ? "scroll" : undefined,
- width: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeWidth(this.props.Document) - this.props.sidebarWidth() / this.contentScaling : `calc(${100 / this.contentScaling}% - ${this.props.sidebarWidth() / this.contentScaling}px)`,
height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
transform: `scale(${this.contentScaling})`
}} >
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 6a2325342..c72b25040 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -530,7 +530,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
<div className="searchBox-lozenge-dashboard" >
<select className="searchBox-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}>
- {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
+ {myDashboards.map((dash, i) => <option key={dash[Id]} value={i} style={{ backgroundColor: "black" }}> {StrCast(dash.title)} </option>)}
</select>
<div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}>
New
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
new file mode 100644
index 000000000..2ecbb536b
--- /dev/null
+++ b/src/client/views/topbar/TopBar.scss
@@ -0,0 +1,217 @@
+@import "../global/globalCssVariables";
+
+.topbar-container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ position: relative;
+ font-size: 10px;
+ line-height: 1;
+ overflow-y: auto;
+ overflow-x: visible;
+ background: $dark-gray;
+ overflow: visible;
+ z-index: 1000;
+
+ .topbar-bar {
+ height: $topbar-height;
+ display: grid;
+ grid-auto-columns: 33.3% 33.3% 33.3%;
+ align-items: center;
+ background-color: $dark-gray;
+
+ .topBar-icon {
+ cursor: pointer;
+ font-size: 12px;
+ font-family: 'Roboto';
+ width: fit-content;
+ display: flex;
+ justify-content: center;
+ gap: 4px;
+ align-items: center;
+ justify-self: center;
+ align-self: center;
+ border-radius: 5px;
+ padding: 5px;
+ transition: linear 0.1s;
+ color: $black;
+ background-color: $light-gray;
+ }
+
+ .topBar-icon:hover {
+ background-color: $light-blue;
+ }
+
+
+ .topbar-center {
+ grid-column: 2;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
+
+ .topbar-dashboards {
+ display: flex;
+ flex-direction: row;
+ }
+
+ .topbar-lozenge-dashboard {
+ display: flex;
+
+
+
+ .topbar-dashSelect {
+ border: none;
+ background-color: $dark-gray;
+ color: $white;
+ font-family: 'Roboto';
+ font-size: 17;
+ font-weight: 500;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+ }
+
+
+ .topbar-right {
+ grid-column: 3;
+ position: relative;
+ display: flex;
+ justify-content: flex-end;
+ gap: 5px;
+ margin-right: 5px;
+ }
+
+ .topbar-left {
+ grid-column: 1;
+ color: black;
+ font-family: 'Roboto';
+ position: relative;
+ display: flex;
+ width: fit-content;
+ gap: 5px;
+
+ .topBar-icon:hover {
+ background-color: $close-red;
+ }
+
+ .topbar-lozenge-user,
+ .topbar-lozenge {
+ height: 23;
+ font-size: 12;
+ color: white;
+ font-family: 'Roboto';
+ font-weight: 400;
+ padding: 4px;
+ align-self: center;
+ margin-left: 7px;
+ display: flex;
+ align-items: center;
+
+ .topbar-dashSelect {
+ border: none;
+ background-color: transparent;
+ color: black;
+ font-family: 'Roboto';
+ font-size: 17;
+ font-weight: 500;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .topbar-logoff {
+ border-radius: 3px;
+ background: olivedrab;
+ color: white;
+ display: none;
+ margin-left: 5px;
+ padding: 1px 2px 1px 2px;
+ cursor: pointer;
+ }
+
+ .topbar-logoff {
+ background: red;
+ }
+
+ .topbar-lozenge-user:hover {
+ .topbar-logoff {
+ display: inline-block;
+ }
+ }
+ }
+
+ .topbar-barChild {
+
+ &.topbar-collection {
+ flex: 0 1 auto;
+ margin-left: 2px;
+ margin-right: 2px
+ }
+
+ &.topbar-input {
+ margin:5px;
+ border-radius:20px;
+ border:$dark-gray;
+ display: block;
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ /* align-self: stretch; */
+ outline: none;
+
+ &:focus {
+ width: 500px;
+ outline: none;
+ }
+ }
+
+ &.topbar-filter {
+ align-self: stretch;
+
+ button {
+ transform: none;
+
+ &:hover {
+ transform: none;
+ }
+ }
+ }
+
+ &.topbar-submit {
+ margin-left: 2px;
+ margin-right: 2px
+ }
+
+ &.topbar-close {
+ color: $white;
+ max-height: $topbar-height;
+ }
+ }
+ }
+}
+
+.topbar-results {
+ display: flex;
+ flex-direction: column;
+ top: 300px;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: visible;
+
+ .no-result {
+ width: 500px;
+ background: $light-gray;
+ padding: 10px;
+ height: 50px;
+ text-transform: uppercase;
+ text-align: left;
+ font-weight: bold;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
new file mode 100644
index 000000000..05edb975c
--- /dev/null
+++ b/src/client/views/topbar/TopBar.tsx
@@ -0,0 +1,66 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { observer } from "mobx-react";
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { StrCast } from '../../../fields/Types';
+import { Utils } from '../../../Utils';
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { SettingsManager } from "../../util/SettingsManager";
+import { undoBatch } from "../../util/UndoManager";
+import { Borders, Colors } from "../global/globalEnums";
+import "./TopBar.scss";
+
+/**
+ * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
+ * and settings and help buttons. Future scope for this bar is to include the collaborators that are on the same Dashboard.
+ */
+@observer
+export class TopBar extends React.Component {
+ render() {
+ const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+ return (
+ //TODO:glr Add support for light / dark mode
+ <div style={{ pointerEvents: "all" }} className="topbar-container">
+ <div className="topbar-bar" style={{ background: Colors.DARK_GRAY, borderBottom: Borders.STANDARD }}>
+ <div className="topbar-left">
+ <div className="topbar-lozenge-user">
+ {`${Doc.CurrentUserEmail}`}
+ </div>
+ <div className="topbar-icon" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
+ {"Sign out"}
+ </div>
+ </div>
+ <div className="topbar-center" >
+ <div className="topbar-lozenge-dashboard">
+ <select className="topbar-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
+ value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}
+ style={{ color: Colors.WHITE }}>
+ {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
+ </select>
+ </div>
+ <div className="topbar-dashboards">
+ <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}
+ >
+ {"New"}<FontAwesomeIcon icon="plus"></FontAwesomeIcon>
+ </div>
+ {Doc.UserDoc().noviceMode ? (null) : <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.snapshotDashboard(Doc.UserDoc()))}
+ >
+ {"Snapshot"}<FontAwesomeIcon icon="camera"></FontAwesomeIcon>
+ </div>}
+ </div>
+ </div>
+ <div className="topbar-right" >
+ <div className="topbar-icon">
+ {"Help"}<FontAwesomeIcon icon="question-circle"></FontAwesomeIcon>
+ </div>
+ <div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}>
+ {"Settings"}<FontAwesomeIcon icon="cog"></FontAwesomeIcon>
+ </div>
+
+ </div>
+ </div>
+ </div >
+ );
+ }
+} \ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7993af149..1eeadeedc 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -21,8 +21,10 @@ import { listSpec } from "./Schema";
import { ComputedField, ScriptField } from "./ScriptField";
import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField";
-import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
+import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
import JSZip = require("jszip");
+import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -53,6 +55,9 @@ export namespace Field {
|| (field instanceof RefField)
|| (includeUndefined && field === undefined);
}
+ export function Copy(field: any) {
+ return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
+ }
}
export type Field = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
@@ -60,10 +65,10 @@ export type FieldWaiting<T extends RefField = RefField> = T extends undefined ?
export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
/**
- * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
- * If a default value is given, that will be returned instead of undefined.
- * If a default value is given, the returned value should not be modified as it might be a temporary value.
- * If no default value is given, and the returned value is not undefined, it can be safely modified.
+ * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
+ * If a default value is given, that will be returned instead of undefined.
+ * If a default value is given, the returned value should not be modified as it might be a temporary value.
+ * If no default value is given, and the returned value is not undefined, it can be safely modified.
*/
export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
@@ -88,7 +93,8 @@ export const DirectLinksSym = Symbol("DirectLinks");
export const AclUnset = Symbol("AclUnset");
export const AclPrivate = Symbol("AclOwnerOnly");
export const AclReadonly = Symbol("AclReadOnly");
-export const AclAddonly = Symbol("AclAddonly");
+export const AclAugment = Symbol("AclAugment");
+export const AclSelfEdit = Symbol("AclSelfEdit");
export const AclEdit = Symbol("AclEdit");
export const AclAdmin = Symbol("AclAdmin");
export const UpdatingFromServer = Symbol("UpdatingFromServer");
@@ -100,7 +106,8 @@ const AclMap = new Map<string, symbol>([
["None", AclUnset],
[SharingPermissions.None, AclPrivate],
[SharingPermissions.View, AclReadonly],
- [SharingPermissions.Add, AclAddonly],
+ [SharingPermissions.Augment, AclAugment],
+ [SharingPermissions.SelfEdit, AclSelfEdit],
[SharingPermissions.Edit, AclEdit],
[SharingPermissions.Admin, AclAdmin]
]);
@@ -250,7 +257,8 @@ export class Doc extends RefField {
DocServer.GetRefField(this[Id], true);
}
};
- if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ const writeMode = DocServer.getFieldWriteMode(fKey as string);
+ if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
await fn();
} else {
@@ -364,13 +372,13 @@ export namespace Doc {
/**
* This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
* the values of the properties of a source object into the target.
- *
+ *
* This is just a specific, Dash-authored version that serves the same role for our
* Doc class.
- *
- * @param doc the target document into which you'd like to insert the new fields
+ *
+ * @param doc the target document into which you'd like to insert the new fields
* @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
- * to a potentially undefined field, where each entry in this mapping is optional.
+ * to a potentially undefined field, where each entry in this mapping is optional.
*/
export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
isInitializing && (doc[Initializing] = true);
@@ -397,7 +405,7 @@ export namespace Doc {
}
// Gets the data document for the document. Note: this is mis-named -- it does not specifically
- // return the doc's proto, but rather recursively searches through the proto inheritance chain
+ // return the doc's proto, but rather recursively searches through the proto inheritance chain
// and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
export function GetProto(doc: Doc): Doc {
if (doc instanceof Promise) {
@@ -423,6 +431,9 @@ export namespace Doc {
return Array.from(results);
}
+ /**
+ * @returns the index of doc toFind in list of docs, -1 otherwise
+ */
export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
@@ -521,7 +532,7 @@ export namespace Doc {
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
- const fieldExclusions = (doc.type === DocumentType.TEXTANCHOR) ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
+ const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
await Promise.all(Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
@@ -529,13 +540,13 @@ export namespace Doc {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
const copyObjectField = async (field: ObjectField) => {
- const list = Cast(doc[key], listSpec(Doc));
+ const list = await Cast(doc[key], listSpec(Doc));
const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
if (docs !== undefined && docs.length) {
const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
!dontCreate && assignKey(new List<Doc>(clones));
} else if (doc[key] instanceof Doc) {
- assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields
+ assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
} else {
!dontCreate && assignKey(ObjectField.MakeCopy(field));
if (field instanceof RichTextField) {
@@ -561,7 +572,7 @@ export namespace Doc {
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
+ debugger; //This shouldn't happen...
} else {
assignKey(field);
}
@@ -581,11 +592,10 @@ export namespace Doc {
}
return copy;
}
- export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false) {
- const cloneMap = new Map<string, Doc>();
+ export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<Doc, Doc>();
const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch);
Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
@@ -596,7 +606,7 @@ export namespace Doc {
const mapped = cloneMap.get(id);
return href + (mapped ? mapped[Id] : id);
};
- const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
+ const regex = `(${Doc.localServerPath()})([^"]*)`;
const re = new RegExp(regex, "g");
copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
@@ -666,14 +676,14 @@ export namespace Doc {
const _pendingMap: Map<string, boolean> = new Map();
//
// Returns an expanded template layout for a target data document if there is a template relationship
- // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
+ // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
// of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
// templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key.
// NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field
// so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and
// the derefence will then occur on the rootDocument (the original document).
// in the future, field references could be written as @<someparam> and then arguments would be passed in the layout key as:
- // layout_mytemplate(somparam=somearg).
+ // layout_mytemplate(somparam=somearg).
// then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
@@ -774,7 +784,7 @@ export namespace Doc {
copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
} else if (field instanceof ObjectField) {
copy[key] = doc[key] instanceof Doc ?
- key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
+ key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
ObjectField.MakeCopy(field);
} else if (field instanceof Promise) {
debugger; //This shouldn't happend...
@@ -896,6 +906,16 @@ export namespace Doc {
return true;
}
+
+ // converts a document id to a url path on the server
+ export function globalServerPath(doc: Doc | string = ""): string {
+ return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc));
+ }
+ // converts a document id to a url path on the server
+ export function localServerPath(doc?: Doc): string {
+ return "/doc/" + (doc ? doc[Id] : "");
+ }
+
export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
const doc2Layout = Doc.Layout(doc2);
const doc1Layout = Doc.Layout(doc1);
@@ -927,7 +947,7 @@ export namespace Doc {
}
// the document containing the view layout information - will be the Document itself unless the Document has
- // a layout field or 'layout' is given.
+ // a layout field or 'layout' is given.
export function Layout(doc: Doc, layout?: Doc): Doc {
const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null);
return overrideLayout || doc[LayoutSym] || doc;
@@ -1106,7 +1126,7 @@ export namespace Doc {
}
// filters document in a container collection:
- // all documents with the specified value for the specified key are included/excluded
+ // all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
if (!container) return;
@@ -1177,6 +1197,9 @@ export namespace Doc {
dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
}
+
+ if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
+
return ndoc;
}
export function delegateDragFactory(dragFactory: Doc) {
@@ -1193,7 +1216,10 @@ export namespace Doc {
case DocumentType.IMG: return "image";
case DocumentType.COMPARISON: return "columns";
case DocumentType.RTF: return "sticky-note";
- case DocumentType.COL: return !doc?.isFolder ? "folder" + (isOpen ? "-open" : "") : "chevron-" + (isOpen ? "down" : "right");
+ case DocumentType.COL:
+ const folder: IconProp = isOpen ? "folder-open" : "folder";
+ const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
+ return !doc?.isFolder ? folder : chevron;
case DocumentType.WEB: return "globe-asia";
case DocumentType.SCREENSHOT: return "photo-video";
case DocumentType.WEBCAM: return "video";
@@ -1227,39 +1253,39 @@ export namespace Doc {
/**
* This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
* deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
- *
+ *
* After building a hierarchy within / below a top-level document, it then returns that top-level parent.
- *
+ *
* If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the
* string is invalid JSON, so we should assume that the input is the result of a JSON.parse()
* call that returned a regular string value to be stored as a Field.
- *
+ *
* If we've received something other than a string, since the caller might also pass in the results of a
* JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number.
* Anything else (like a function, etc. passed in naively as any) is meaningless for this operation.
- *
+ *
* All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
* lacking the key value structure, gets stored as a field in a wrapper document.
- *
+ *
* @param data for convenience and flexibility, either a valid JSON string to be parsed,
* or the result of any JSON.parse() call.
* @param title an optional title to give to the highest parent document in the hierarchy.
* If whether this function creates a new document or appendToExisting is specified and that document already has a title,
* because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
* @param appendToExisting **if specified**, there are two cases, both of which return the target document:
- *
+ *
* 1) the json to be converted can be represented as a document, in which case the target document will act as the root
* of the tree and receive all the conversion results as new fields on itself
* 2) the json can't be represented as a document, in which case the function will assign the field-level conversion
* results to either the specified key on the target document, or to its "json" key by default.
- *
+ *
* If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls)
* to act as the root of the tree.
- *
+ *
* One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
* say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
* from a default call to new Doc.
- *
+ *
* @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
* if they contain no data. By default, empty objects and arrays are ignored.
*/
@@ -1295,7 +1321,7 @@ export namespace Doc {
* For each value of the object, recursively convert it to its appropriate field value
* and store the field at the appropriate key in the document if it is not undefined
* @param object the object to convert
- * @returns the object mapped from JSON to field values, where each mapping
+ * @returns the object mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertObject)
*/
const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
@@ -1319,10 +1345,10 @@ export namespace Doc {
};
/**
- * For each element in the list, recursively convert it to a document or other field
+ * For each element in the list, recursively convert it to a document or other field
* and push the field to the list if it is not undefined
* @param list the list to convert
- * @returns the list mapped from JSON to field values, where each mapping
+ * @returns the list mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertList)
*/
const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
@@ -1361,7 +1387,7 @@ Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
Scripting.addGlobal(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); });
-Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
+Scripting.addGlobal(function copyField(field: any) { return Field.Copy(field); });
Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 215dff34b..93a8d1d60 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from "mobx";
+import { action, observable } from "mobx";
import { alias, list, serializable } from "serializr";
import { DocServer } from "../client/DocServer";
import { Scripting } from "../client/util/Scripting";
@@ -264,24 +264,19 @@ class ListImpl<T extends Field> extends ObjectField {
// this requests all ProxyFields at the same time to avoid the overhead
// of separate network requests and separate updates to the React dom.
private __realFields() {
- const waiting = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue());
- const promised = waiting.map(f => f instanceof ProxyField ? f.promisedValue() : "");
+ const promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: (f instanceof ProxyField) ? f.promisedValue() : "" }));
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
if (promised.length) {
- const promise = DocServer.GetRefFields(promised);
+ const batchPromise = DocServer.GetRefFields(promised.map(p => p.promisedFieldId));
// as soon as we get the fields from the server, set all the list values in one
// action to generate one React dom update.
- promise.then(fields => runInAction(() => {
- waiting.map((w, i) => w instanceof ProxyField && w.setValue(fields[promised[i]]));
- }));
+ batchPromise.then(pfields => promised.forEach(p => p.field.setValue(pfields[p.promisedFieldId])));
// we also have to mark all lists items with this promise so that any calls to them
- // will await the batch request.
- // This counts on the handler for 'promise' in the call above being invoked before the
+ // will await the batch request and return the requested field value.
+ // This assumes the handler for 'promise' in the call above being invoked before the
// handler for 'promise' in the lines below.
- waiting.map((w, i) => {
- w instanceof ProxyField && w.setPromise(promise.then(fields => fields[promised[i]]));
- });
+ promised.forEach(p => p.field.setPromise(batchPromise.then(pfields => pfields[p.promisedFieldId])));
}
return this.__fields.map(toRealField);
}
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index fb71160ca..d96e8a70a 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -3,14 +3,17 @@ import { serializable, custom } from "serializr";
import { ObjectField } from "./ObjectField";
import { ToScriptString, ToString, Copy } from "./FieldSymbols";
import { Scripting, scriptingGlobal } from "../client/util/Scripting";
+import { Utils } from "../Utils";
function url() {
return custom(
function (value: URL) {
- return value.href;
+ return value.origin === window.location.origin ?
+ value.pathname :
+ value.href;
},
function (jsonValue: string) {
- return new URL(jsonValue);
+ return new URL(jsonValue, window.location.origin);
}
);
}
@@ -24,15 +27,21 @@ export abstract class URLField extends ObjectField {
constructor(url: URL | string) {
super();
if (typeof url === "string") {
- url = new URL(url);
+ url = url.startsWith("http") ? new URL(url) : new URL(url, window.location.origin);
}
this.url = url;
}
[ToScriptString]() {
+ if (Utils.prepend(this.url.pathname) === this.url.href) {
+ return `new ${this.constructor.name}("${this.url.pathname}")`;
+ }
return `new ${this.constructor.name}("${this.url.href}")`;
}
[ToString]() {
+ if (Utils.prepend(this.url.pathname) === this.url.href) {
+ return this.url.pathname;
+ }
return this.url.href;
}
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ea91cc057..439c4d333 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAugment, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing, AclSelfEdit } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
@@ -14,6 +14,7 @@ import CursorField from "./CursorField";
import { List } from "./List";
import { SnappingManager } from "../client/util/SnappingManager";
import { computedFn } from "mobx-utils";
+import { RichTextField } from "./RichTextField";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -77,7 +78,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !DocServer.Control.isReadOnly();// && !playgroundMode;
+ const writeToServer =
+ (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && (value instanceof RichTextField))) &&
+ !DocServer.Control.isReadOnly();
if (writeToDoc) {
if (value === undefined) {
@@ -131,6 +134,19 @@ export function denormalizeEmail(email: string) {
// playgroundMode = !playgroundMode;
// }
+
+/**
+ * Copies parent's acl fields to the child
+ */
+export function inheritParentAcls(parent: Doc, child: Doc) {
+ const dataDoc = parent[DataSym];
+ for (const key of Object.keys(dataDoc)) {
+ // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
+ const permission = (key === "acl-Public" && Doc.UserDoc().defaultAclPrivate) ? AclPrivate : dataDoc[key];
+ key.startsWith("acl") && distributeAcls(key, permission, child);
+ }
+}
+
/**
* These are the various levels of access a user can have to a document.
*
@@ -146,9 +162,10 @@ export function denormalizeEmail(email: string) {
*/
export enum SharingPermissions {
Admin = "Admin",
- Edit = "Can Edit",
- Add = "Can Augment",
- View = "Can View",
+ Edit = "Edit",
+ SelfEdit = "Self Edit",
+ Augment = "Augment",
+ View = "View",
None = "Not Shared"
}
@@ -165,7 +182,7 @@ export function GetEffectiveAcl(target: any, user?: string): symbol {
function getPropAcl(target: any, prop: string | symbol | number) {
if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
- if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable
+ if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
return GetEffectiveAcl(target);
}
@@ -181,7 +198,8 @@ function getEffectiveAcl(target: any, user?: string): symbol {
HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
[AclPrivate, 0],
[AclReadonly, 1],
- [AclAddonly, 2],
+ [AclAugment, 2],
+ [AclSelfEdit, 2.5],
[AclEdit, 3],
[AclAdmin, 4]
]);
@@ -215,7 +233,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
* @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection)
* inheritingFromCollection is not currently being used but could be used if acl assignment defaults change
*/
-export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[]) {
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) {
if (!visited) visited = [] as Doc[];
if (visited.includes(target)) return;
visited.push(target);
@@ -224,6 +242,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
["Not Shared", 0],
["Can View", 1],
["Can Augment", 2],
+ ["Self Edit", 2.5],
["Can Edit", 3],
["Admin", 4]
]);
@@ -236,6 +255,12 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!)) {
target[key] = acl;
layoutDocChanged = true;
+
+ if (isDashboard) {
+ DocListCastAsync(target[Doc.LayoutFieldKey(target)]).then(docs => {
+ docs?.forEach(d => distributeAcls(key, acl, d, inheritingFromCollection, visited));
+ });
+ }
}
if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
@@ -245,28 +270,26 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
dataDocChanged = true;
}
- // maps over the aliases of the document
+ // maps over the links of the document
const links = DocListCast(dataDoc.links);
links.forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
// maps over the children of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
- // if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? "-all" : "")]).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
- if (data) {// && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
+ if (data) {
distributeAcls(key, acl, data, inheritingFromCollection, visited);
}
});
// maps over the annotations of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
- // if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
- if (data) {// && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
+ if (data) {
distributeAcls(key, acl, data, inheritingFromCollection, visited);
}
});
@@ -279,7 +302,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
const effectiveAcl = getPropAcl(target, prop);
- if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
+ if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
// if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true;
// if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 98696496f..f910d765e 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -50,7 +50,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
if (result instanceof Error) {
return;
}
- const path = Utils.prepend(result.accessPaths.agnostic.client);
+ const path = result.accessPaths.agnostic.client;
let doc = null;
// Case 1: File is a video
if (file.type === "video/mp4") {
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index e40f2b8e5..0f4a067fc 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -142,8 +142,9 @@ function registerCorsProxy(server: express.Express) {
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
server.use("/corsProxy", async (req, res) => {
- const requrl = decodeURIComponent(req.url.substring(1));
const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : "";
+ const requrlraw = decodeURIComponent(req.url.substring(1));
+ const requrl = requrlraw.startsWith("/") ? referer + requrlraw : requrlraw;
// cors weirdness here...
// if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
// then we redirect again to the cors referer and just add the relative path.
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 4ae97913f..224a9eefb 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -283,7 +283,7 @@ export namespace WebSocket {
return;
}
const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || [];
- diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+ diff.diff.$set[updatefield].fields = [...curList, ...newListItems];//, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
delete diff.diff.length;
Database.Instance.update(diff.id, diff.diff,