aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts12
-rw-r--r--src/client/ClientRecommender.tsx16
-rw-r--r--src/client/DocServer.ts72
-rw-r--r--src/client/apis/GoogleAuthenticationManager.scss7
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx126
-rw-r--r--src/client/apis/IBM_Recommender.ts2
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts4
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts45
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx4
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts8
-rw-r--r--src/client/documents/Documents.ts118
-rw-r--r--src/client/util/CurrentUserUtils.ts (renamed from src/server/authentication/models/current_user_utils.ts)233
-rw-r--r--src/client/util/DictationManager.ts12
-rw-r--r--src/client/util/DocumentManager.ts46
-rw-r--r--src/client/util/DragManager.ts198
-rw-r--r--src/client/util/DropConverter.ts16
-rw-r--r--src/client/util/History.ts2
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx12
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts8
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx4
-rw-r--r--src/client/util/InteractionUtils.tsx8
-rw-r--r--src/client/util/LinkManager.ts8
-rw-r--r--src/client/util/Scripting.ts4
-rw-r--r--src/client/util/SearchUtil.ts4
-rw-r--r--src/client/util/SelectionManager.ts10
-rw-r--r--src/client/util/SerializationHelper.ts2
-rw-r--r--src/client/util/SettingsManager.tsx5
-rw-r--r--src/client/util/SharingManager.tsx6
-rw-r--r--src/client/util/SnappingManager.ts29
-rw-r--r--src/client/views/DocComponent.tsx69
-rw-r--r--src/client/views/DocumentButtonBar.tsx103
-rw-r--r--src/client/views/DocumentDecorations.scss51
-rw-r--r--src/client/views/DocumentDecorations.tsx136
-rw-r--r--src/client/views/EditableView.tsx4
-rw-r--r--src/client/views/GestureOverlay.tsx38
-rw-r--r--src/client/views/GlobalKeyHandler.ts76
-rw-r--r--src/client/views/InkingControl.tsx26
-rw-r--r--src/client/views/InkingStroke.tsx12
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.scss19
-rw-r--r--src/client/views/MainView.tsx79
-rw-r--r--src/client/views/MainViewModal.tsx2
-rw-r--r--src/client/views/MainViewNotifs.tsx2
-rw-r--r--src/client/views/MetadataEntryMenu.scss9
-rw-r--r--src/client/views/MetadataEntryMenu.tsx2
-rw-r--r--src/client/views/OverlayView.tsx10
-rw-r--r--src/client/views/Palette.tsx4
-rw-r--r--src/client/views/PreviewCursor.tsx96
-rw-r--r--src/client/views/RecommendationsBox.tsx8
-rw-r--r--src/client/views/ScriptBox.tsx10
-rw-r--r--src/client/views/SearchDocBox.tsx16
-rw-r--r--src/client/views/TemplateMenu.tsx18
-rw-r--r--src/client/views/animationtimeline/Keyframe.tsx8
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx49
-rw-r--r--src/client/views/animationtimeline/TimelineMenu.tsx81
-rw-r--r--src/client/views/animationtimeline/Track.tsx62
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx33
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx110
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx10
-rw-r--r--src/client/views/collections/CollectionMapView.tsx25
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx12
-rw-r--r--src/client/views/collections/CollectionPileView.tsx15
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx18
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx20
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx34
-rw-r--r--src/client/views/collections/CollectionStackingView.scss7
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx96
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx28
-rw-r--r--src/client/views/collections/CollectionStaffView.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx145
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx16
-rw-r--r--src/client/views/collections/CollectionTreeView.scss1
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx251
-rw-r--r--src/client/views/collections/CollectionView.scss2
-rw-r--r--src/client/views/collections/CollectionView.tsx120
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx16
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx14
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx22
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx318
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx80
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx53
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx54
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx18
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx4
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx4
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx18
-rw-r--r--src/client/views/linking/LinkEditor.tsx4
-rw-r--r--src/client/views/linking/LinkMenu.tsx2
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx10
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx41
-rw-r--r--src/client/views/nodes/AudioBox.tsx30
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx36
-rw-r--r--src/client/views/nodes/ColorBox.tsx14
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx63
-rw-r--r--src/client/views/nodes/DocHolderBox.scss (renamed from src/client/views/nodes/DocumentBox.scss)3
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx213
-rw-r--r--src/client/views/nodes/DocumentBox.tsx157
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx62
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx246
-rw-r--r--src/client/views/nodes/FaceRectangles.tsx6
-rw-r--r--src/client/views/nodes/FieldView.tsx20
-rw-r--r--src/client/views/nodes/FontIconBox.tsx25
-rw-r--r--src/client/views/nodes/ImageBox.tsx62
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx16
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx2
-rw-r--r--src/client/views/nodes/LabelBox.scss14
-rw-r--r--src/client/views/nodes/LabelBox.tsx17
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx11
-rw-r--r--src/client/views/nodes/LinkBox.tsx6
-rw-r--r--src/client/views/nodes/PDFBox.tsx12
-rw-r--r--src/client/views/nodes/PresBox.scss29
-rw-r--r--src/client/views/nodes/PresBox.tsx244
-rw-r--r--src/client/views/nodes/QueryBox.tsx14
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx12
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx12
-rw-r--r--src/client/views/nodes/SliderBox.tsx8
-rw-r--r--src/client/views/nodes/VideoBox.tsx27
-rw-r--r--src/client/views/nodes/WebBox.scss56
-rw-r--r--src/client/views/nodes/WebBox.tsx68
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx16
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx10
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx38
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx399
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx22
-rw-r--r--src/client/views/nodes/formattedText/ImageResizeView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts6
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss43
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx13
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts14
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx17
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts2
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts3
-rw-r--r--src/client/views/pdf/Annotation.tsx8
-rw-r--r--src/client/views/pdf/PDFMenu.tsx2
-rw-r--r--src/client/views/pdf/PDFViewer.tsx20
-rw-r--r--src/client/views/presentationview/PresElementBox.scss43
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx150
-rw-r--r--src/client/views/search/FilterBox.tsx6
-rw-r--r--src/client/views/search/SearchBox.tsx8
-rw-r--r--src/client/views/search/SearchItem.tsx18
-rw-r--r--src/debug/Repl.tsx6
-rw-r--r--src/debug/Test.tsx2
-rw-r--r--src/debug/Viewer.tsx16
-rw-r--r--src/fields/CursorField.ts (renamed from src/new_fields/CursorField.ts)0
-rw-r--r--src/fields/DateField.ts (renamed from src/new_fields/DateField.ts)0
-rw-r--r--src/fields/Doc.ts (renamed from src/new_fields/Doc.ts)103
-rw-r--r--src/fields/FieldSymbols.ts (renamed from src/new_fields/FieldSymbols.ts)0
-rw-r--r--src/fields/HtmlField.ts (renamed from src/new_fields/HtmlField.ts)0
-rw-r--r--src/fields/IconField.ts (renamed from src/new_fields/IconField.ts)0
-rw-r--r--src/fields/InkField.ts (renamed from src/new_fields/InkField.ts)0
-rw-r--r--src/fields/List.ts (renamed from src/new_fields/List.ts)76
-rw-r--r--src/fields/ListSpec.ts (renamed from src/new_fields/ListSpec.ts)0
-rw-r--r--src/fields/ObjectField.ts (renamed from src/new_fields/ObjectField.ts)0
-rw-r--r--src/fields/PresField.ts (renamed from src/new_fields/PresField.ts)0
-rw-r--r--src/fields/Proxy.ts (renamed from src/new_fields/Proxy.ts)18
-rw-r--r--src/fields/RefField.ts (renamed from src/new_fields/RefField.ts)0
-rw-r--r--src/fields/RichTextField.ts (renamed from src/new_fields/RichTextField.ts)0
-rw-r--r--src/fields/RichTextUtils.ts (renamed from src/new_fields/RichTextUtils.ts)0
-rw-r--r--src/fields/Schema.ts (renamed from src/new_fields/Schema.ts)0
-rw-r--r--src/fields/SchemaHeaderField.ts (renamed from src/new_fields/SchemaHeaderField.ts)0
-rw-r--r--src/fields/ScriptField.ts (renamed from src/new_fields/ScriptField.ts)32
-rw-r--r--src/fields/Types.ts (renamed from src/new_fields/Types.ts)4
-rw-r--r--src/fields/URLField.ts (renamed from src/new_fields/URLField.ts)0
-rw-r--r--src/fields/documentSchemas.ts (renamed from src/new_fields/documentSchemas.ts)99
-rw-r--r--src/fields/util.ts (renamed from src/new_fields/util.ts)5
-rw-r--r--src/mobile/ImageUpload.tsx11
-rw-r--r--src/mobile/MobileInkOverlay.tsx6
-rw-r--r--src/mobile/MobileInterface.tsx16
-rw-r--r--src/pen-gestures/GestureUtils.ts6
-rw-r--r--src/scraping/buxton/final/BuxtonImporter.ts248
-rw-r--r--src/scraping/buxton/scraper.py2
-rw-r--r--src/server/ApiManagers/ApiManager.ts2
-rw-r--r--src/server/ApiManagers/DeleteManager.ts87
-rw-r--r--src/server/ApiManagers/GeneralGoogleManager.ts19
-rw-r--r--src/server/ApiManagers/GooglePhotosManager.ts4
-rw-r--r--src/server/ApiManagers/SessionManager.ts2
-rw-r--r--src/server/ApiManagers/UploadManager.ts2
-rw-r--r--src/server/ApiManagers/UserManager.ts4
-rw-r--r--src/server/DashSession/DashSessionAgent.ts4
-rw-r--r--src/server/DashUploadUtils.ts9
-rw-r--r--src/server/GarbageCollector.ts6
-rw-r--r--src/server/IDatabase.ts4
-rw-r--r--src/server/MemoryDatabase.ts23
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/Recommender.ts6
-rw-r--r--src/server/RouteManager.ts2
-rw-r--r--src/server/apis/google/CredentialsLoader.ts (renamed from src/server/credentials/CredentialsLoader.ts)0
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts57
-rw-r--r--src/server/apis/google/google_project_credentials.json (renamed from src/server/credentials/google_project_credentials.json)0
-rw-r--r--src/server/apis/youtube/youtubeApiSample.js22
-rw-r--r--src/server/authentication/AuthenticationManager.ts (renamed from src/server/authentication/controllers/user_controller.ts)8
-rw-r--r--src/server/authentication/DashUserModel.ts (renamed from src/server/authentication/models/user_model.ts)8
-rw-r--r--src/server/authentication/Passport.ts (renamed from src/server/authentication/config/passport.ts)2
-rw-r--r--src/server/database.ts191
-rw-r--r--src/server/index.ts7
-rw-r--r--src/server/remapUrl.ts4
-rw-r--r--src/server/server_Initialization.ts4
-rw-r--r--src/server/websocket.ts (renamed from src/server/Websocket/Websocket.ts)74
204 files changed, 4226 insertions, 2957 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index ad12c68a1..bcb215804 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -4,7 +4,7 @@ import { Socket, Room } from 'socket.io';
import { Message } from './server/Message';
export namespace Utils {
- export const DRAG_THRESHOLD = 4;
+ export let DRAG_THRESHOLD = 4;
export function GenerateGuid(): string {
return v4();
@@ -313,14 +313,18 @@ export namespace Utils {
}
}
-export function OmitKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
+export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
const omit: any = { ...obj };
const extract: any = {};
keys.forEach(key => {
extract[key] = omit[key];
delete omit[key];
});
- addKeyFunc && addKeyFunc(omit);
+ pattern && Array.from(Object.keys(omit)).filter(key => key.match(pattern)).forEach(key => {
+ extract[key] = omit[key];
+ delete omit[key];
+ });
+ addKeyFunc?.(omit);
return { omit, extract };
}
@@ -512,7 +516,7 @@ export function setupMoveUpEvents(
(target as any)._downY = (target as any)._lastY = e.clientY;
const _moveEvent = (e: PointerEvent): void => {
- if (Math.abs(e.clientX - (target as any)._downX) > 4 || Math.abs(e.clientY - (target as any)._downY) > 4) {
+ if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
if (moveEvent(e, [(target as any)._downX, (target as any)._downY],
[e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) {
document.removeEventListener("pointermove", _moveEvent);
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 537e331ab..d18669b02 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -1,6 +1,6 @@
-import { Doc, FieldResult } from "../new_fields/Doc";
-import { StrCast, Cast } from "../new_fields/Types";
-import { List } from "../new_fields/List";
+import { Doc, FieldResult } from "../fields/Doc";
+import { StrCast, Cast } from "../fields/Types";
+import { List } from "../fields/List";
import { CognitiveServices, Confidence, Tag, Service } from "./cognitive_services/CognitiveServices";
import React = require("react");
import { observer } from "mobx-react";
@@ -11,11 +11,11 @@ import { observable, action, computed, reaction } from "mobx";
// var https = require('https');
import "./ClientRecommender.scss";
import { JSXElement } from "babel-types";
-import { RichTextField } from "../new_fields/RichTextField";
-import { ToPlainText } from "../new_fields/FieldSymbols";
-import { listSpec } from "../new_fields/Schema";
-import { ComputedField } from "../new_fields/ScriptField";
-import { ImageField } from "../new_fields/URLField";
+import { RichTextField } from "../fields/RichTextField";
+import { ToPlainText } from "../fields/FieldSymbols";
+import { listSpec } from "../fields/Schema";
+import { ComputedField } from "../fields/ScriptField";
+import { ImageField } from "../fields/URLField";
import { KeyphraseQueryView } from "./views/KeyphraseQueryView";
import { Networking } from "./Network";
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 0c9d5f75c..ac5b7a218 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,12 +1,13 @@
import * as OpenSocket from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc } from '../new_fields/Doc';
+import { Opt, Doc } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
-import { RefField } from '../new_fields/RefField';
-import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
+import { RefField } from '../fields/RefField';
+import { Id, HandleUpdate } from '../fields/FieldSymbols';
import GestureOverlay from './views/GestureOverlay';
import MobileInkOverlay from '../mobile/MobileInkOverlay';
+import { runInAction } from 'mobx';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -107,8 +108,9 @@ export namespace DocServer {
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
GUID = identifier;
- _socket = OpenSocket(`${protocol}//${hostname}:${port}`);
+ _socket = OpenSocket(`${protocol}//${hostname}:${port}`);// OpenSocket(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
+ _GetCachedRefField = _GetCachedRefFieldImpl;
_GetRefField = _GetRefFieldImpl;
_GetRefFields = _GetRefFieldsImpl;
_CreateField = _CreateFieldImpl;
@@ -243,12 +245,22 @@ export namespace DocServer {
return Promise.resolve(cached);
}
};
+ const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
+ const cached = _cache[id];
+ if (cached !== undefined && !(cached instanceof Promise)) {
+ return cached;
+ }
+ };
let _GetRefField: (id: string) => Promise<Opt<RefField>> = errorFunc;
+ let _GetCachedRefField: (id: string) => Opt<RefField> = errorFunc;
export function GetRefField(id: string): Promise<Opt<RefField>> {
return _GetRefField(id);
}
+ export function GetCachedRefField(id: string): Opt<RefField> {
+ return _GetCachedRefField(id);
+ }
export async function getYoutubeChannels() {
const apiKey = await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
@@ -308,32 +320,34 @@ export namespace DocServer {
const deserializeFields = getSerializedFields.then(async fields => {
const fieldMap: { [id: string]: RefField } = {};
const proms: Promise<void>[] = [];
- for (const field of fields) {
- if (field !== undefined && field !== null) {
- // deserialize
- const prom = SerializationHelper.Deserialize(field).then(deserialized => {
- fieldMap[field.id] = deserialized;
-
- //overwrite or delete any promises (that we inserted as flags
- // to indicate that the field was in the process of being fetched). Now everything
- // should be an actual value within or entirely absent from the cache.
- if (deserialized !== undefined) {
- _cache[field.id] = deserialized;
- } else {
- delete _cache[field.id];
- }
- return deserialized;
- });
- // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
- // we set the value at the field's id to a promise that will resolve to the field.
- // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
- // The mapping in the .then call ensures that when other callers await these promises, they'll
- // get the resolved field
- _cache[field.id] = prom;
- // adds to a list of promises that will be awaited asynchronously
- proms.push(prom);
+ runInAction(() => {
+ for (const field of fields) {
+ if (field !== undefined && field !== null) {
+ // deserialize
+ const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+ fieldMap[field.id] = deserialized;
+
+ //overwrite or delete any promises (that we inserted as flags
+ // to indicate that the field was in the process of being fetched). Now everything
+ // should be an actual value within or entirely absent from the cache.
+ if (deserialized !== undefined) {
+ _cache[field.id] = deserialized;
+ } else {
+ delete _cache[field.id];
+ }
+ return deserialized;
+ });
+ // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+ // we set the value at the field's id to a promise that will resolve to the field.
+ // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+ // The mapping in the .then call ensures that when other callers await these promises, they'll
+ // get the resolved field
+ _cache[field.id] = prom;
+ // adds to a list of promises that will be awaited asynchronously
+ proms.push(prom);
+ }
}
- }
+ });
await Promise.all(proms);
return fieldMap;
});
diff --git a/src/client/apis/GoogleAuthenticationManager.scss b/src/client/apis/GoogleAuthenticationManager.scss
index 13bde822d..bd30dd94f 100644
--- a/src/client/apis/GoogleAuthenticationManager.scss
+++ b/src/client/apis/GoogleAuthenticationManager.scss
@@ -16,4 +16,11 @@
font-style: italic;
margin-top: 15px;
}
+
+ .disconnect {
+ font-size: 10px;
+ margin-top: 20px;
+ color: red;
+ cursor: grab;
+ }
} \ No newline at end of file
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 417dc3c3b..bf4469aeb 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,10 +1,11 @@
-import { observable, action, reaction, runInAction } from "mobx";
+import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
-import { Opt } from "../../new_fields/Doc";
+import { Opt } from "../../fields/Doc";
import { Networking } from "../Network";
import "./GoogleAuthenticationManager.scss";
+import { Scripting } from "../util/Scripting";
const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
const prompt = "Paste authorization code here...";
@@ -15,64 +16,88 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
private authenticationLink: Opt<string> = undefined;
@observable private openState = false;
@observable private authenticationCode: Opt<string> = undefined;
- @observable private clickedState = false;
+ @observable private showPasteTargetState = false;
@observable private success: Opt<boolean> = undefined;
@observable private displayLauncher = true;
- @observable private avatar: Opt<string> = undefined;
- @observable private username: Opt<string> = undefined;
+ @observable private credentials: any;
+ private disposer: Opt<IReactionDisposer>;
private set isOpen(value: boolean) {
runInAction(() => this.openState = value);
}
- private set hasBeenClicked(value: boolean) {
- runInAction(() => this.clickedState = value);
+ private set shouldShowPasteTarget(value: boolean) {
+ runInAction(() => this.showPasteTargetState = value);
}
- public fetchOrGenerateAccessToken = async () => {
- const response = await Networking.FetchFromServer("/readGoogleAccessToken");
+ public cancel() {
+ this.openState && this.resetState(0, 0);
+ }
+
+ public fetchOrGenerateAccessToken = async (displayIfFound = false) => {
+ let response: any = await Networking.FetchFromServer("/readGoogleAccessToken");
// if this is an authentication url, activate the UI to register the new access token
if (new RegExp(AuthenticationUrl).test(response)) {
this.isOpen = true;
this.authenticationLink = response;
return new Promise<string>(async resolve => {
- const disposer = reaction(
+ this.disposer?.();
+ this.disposer = reaction(
() => this.authenticationCode,
async authenticationCode => {
- if (authenticationCode) {
- disposer();
- const { access_token, avatar, name } = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+ if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
+ this.disposer?.();
+ const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
runInAction(() => {
- this.avatar = avatar;
- this.username = name;
- this.hasBeenClicked = false;
- this.success = false;
+ this.success = true;
+ this.credentials = response;
});
- this.beginFadeout();
- resolve(access_token);
+ this.resetState();
+ resolve(response.access_token);
}
}
);
});
}
- // otherwise, we already have a valid, stored access token
- return response;
+
+ // otherwise, we already have a valid, stored access token and user info
+ response = JSON.parse(response);
+ if (displayIfFound) {
+ runInAction(() => {
+ this.success = true;
+ this.credentials = response;
+ });
+ this.resetState(-1, -1);
+ this.isOpen = true;
+ }
+ return response.access_token;
}
- beginFadeout = action(() => {
- this.success = true;
+ resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
+ if (!visibleForMS && !fadesOutInMS) {
+ runInAction(() => {
+ this.isOpen = false;
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = undefined;
+ this.shouldShowPasteTarget = false;
+ this.authenticationCode = undefined;
+ });
+ return;
+ }
this.authenticationCode = undefined;
this.displayLauncher = false;
- this.hasBeenClicked = false;
- setTimeout(action(() => {
- this.isOpen = false;
+ this.shouldShowPasteTarget = false;
+ if (visibleForMS > 0 && fadesOutInMS > 0) {
setTimeout(action(() => {
- this.success = undefined;
- this.displayLauncher = true;
- this.avatar = undefined;
- this.username = undefined;
- }), 500);
- }), 3000);
+ this.isOpen = false;
+ setTimeout(action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = undefined;
+ }), fadesOutInMS);
+ }), visibleForMS);
+ }
});
constructor(props: {}) {
@@ -83,27 +108,38 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
private get renderPrompt() {
return (
<div className={'authorize-container'}>
+
{this.displayLauncher ? <button
className={"dispatch"}
onClick={() => {
window.open(this.authenticationLink);
- setTimeout(() => this.hasBeenClicked = true, 500);
+ setTimeout(() => this.shouldShowPasteTarget = true, 500);
}}
- style={{ marginBottom: this.clickedState ? 15 : 0 }}
+ style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
>Authorize a Google account...</button> : (null)}
- {this.clickedState ? <input
+ {this.showPasteTargetState ? <input
className={'paste-target'}
onChange={action(e => this.authenticationCode = e.currentTarget.value)}
placeholder={prompt}
/> : (null)}
- {this.avatar ? <img
- className={'avatar'}
- src={this.avatar}
- /> : (null)}
- {this.username ? <span
- className={'welcome'}
- >Welcome to Dash, {this.username}
- </span> : (null)}
+ {this.credentials ?
+ <>
+ <img
+ className={'avatar'}
+ src={this.credentials.userInfo.picture}
+ />
+ <span
+ className={'welcome'}
+ >Welcome to Dash, {this.credentials.userInfo.name}
+ </span>
+ <div
+ className={'disconnect'}
+ onClick={async () => {
+ await Networking.FetchFromServer("/revokeGoogleAccessToken");
+ this.resetState(0, 0);
+ }}
+ >Disconnect Account</div>
+ </> : (null)}
</div>
);
}
@@ -125,4 +161,6 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
);
}
-} \ No newline at end of file
+}
+
+Scripting.addGlobal("GoogleAuthenticationManager", GoogleAuthenticationManager); \ No newline at end of file
diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts
index 4e1c541c8..480b9cb1c 100644
--- a/src/client/apis/IBM_Recommender.ts
+++ b/src/client/apis/IBM_Recommender.ts
@@ -1,4 +1,4 @@
-// import { Opt } from "../../new_fields/Doc";
+// import { Opt } from "../../fields/Doc";
// const NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1');
// const { IamAuthenticator } = require('ibm-watson/auth');
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index fa67ddbef..551dca073 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,5 +1,5 @@
import { docs_v1 } from "googleapis";
-import { Opt } from "../../../new_fields/Doc";
+import { Opt } from "../../../fields/Doc";
import { isArray } from "util";
import { EditorState } from "prosemirror-state";
import { Networking } from "../../Network";
@@ -95,7 +95,7 @@ export namespace GoogleApiClientUtils {
export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] };
export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => {
const paragraphs = extractParagraphs(document);
- let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => run as docs_v1.Schema$TextRun).join("")).join("");
+ let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => (run as docs_v1.Schema$TextRun).content).join("")).join("");
text = text.substring(0, text.length - 1);
removeNewlines && text.replace(/\n/g, "");
return { text, paragraphs };
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index e3f801c46..fef71ffeb 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,11 +1,11 @@
import { AssertionError } from "assert";
import { EditorState } from "prosemirror-state";
-import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { RichTextUtils } from "../../../new_fields/RichTextUtils";
-import { Cast, StrCast } from "../../../new_fields/Types";
-import { ImageField } from "../../../new_fields/URLField";
+import { Doc, DocListCastAsync, Opt } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { RichTextField } from "../../../fields/RichTextField";
+import { RichTextUtils } from "../../../fields/RichTextUtils";
+import { Cast, StrCast } from "../../../fields/Types";
+import { ImageField } from "../../../fields/URLField";
import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/SharedTypes";
import { Utils } from "../../../Utils";
import { Docs, DocumentOptions } from "../../documents/Documents";
@@ -76,7 +76,6 @@ export namespace GooglePhotos {
}
export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise<Opt<AlbumCreationResult>> => {
- await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
const { collection, title, descriptionKey, tag } = options;
const dataDocument = Doc.GetProto(collection);
const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField));
@@ -154,27 +153,22 @@ export namespace GooglePhotos {
}
const tagMapping = new Map<string, string>();
const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto);
- images && images.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
- const values = Object.values(ContentCategories);
+ images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
+ const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE);
for (const value of values) {
- if (value !== ContentCategories.NONE) {
- const results = await ContentSearch({ included: [value] });
- if (results.mediaItems) {
- const ids = results.mediaItems.map(item => item.id);
- for (const id of ids) {
- const image = await Cast(idMapping[id], Doc);
- if (image) {
- const key = image[Id];
- const tags = tagMapping.get(key)!;
- if (!tags.includes(value)) {
- tagMapping.set(key, tags + delimiter + value);
- }
- }
- }
+ const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id);
+ console.log("Searching " + value);
+ console.log(searched);
+ searched?.forEach(async id => {
+ const image = await Cast(idMapping[id], Doc);
+ if (image) {
+ const key = image[Id];
+ const tags = tagMapping.get(key);
+ !tags?.includes(value) && tagMapping.set(key, tags + delimiter + value);
}
- }
+ });
}
- images && images.forEach(image => {
+ images?.forEach(image => {
const concatenated = tagMapping.get(image[Id])!;
const tags = concatenated.split(delimiter);
if (tags.length > 1) {
@@ -184,7 +178,6 @@ export namespace GooglePhotos {
image.googlePhotosTags = ContentCategories.NONE;
}
});
-
};
interface DateRange {
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index 1575e53fc..ce7f49e64 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -1,7 +1,7 @@
import { action, observable, runInAction } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync } from "../../../new_fields/Doc";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, DocListCastAsync } from "../../../fields/Doc";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from "../../documents/Documents";
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 8c63ae906..d4df7ce57 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,12 +1,12 @@
import * as request from "request-promise";
-import { Doc, Field } from "../../new_fields/Doc";
-import { Cast } from "../../new_fields/Types";
+import { Doc, Field } from "../../fields/Doc";
+import { Cast } from "../../fields/Types";
import { Docs } from "../documents/Documents";
import { Utils } from "../../Utils";
-import { InkData } from "../../new_fields/InkField";
+import { InkData } from "../../fields/InkField";
import { UndoManager } from "../util/UndoManager";
import requestPromise = require("request-promise");
-import { List } from "../../new_fields/List";
+import { List } from "../../fields/List";
import { ClientRecommender } from "../ClientRecommender";
type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor };
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c4cc23d21..471045400 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -9,14 +9,14 @@ import { ScriptingBox } from "../views/nodes/ScriptingBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
import { OmitKeys, JSONUtils, Utils } from "../../Utils";
-import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../new_fields/Doc";
-import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField";
-import { HtmlField } from "../../new_fields/HtmlField";
-import { List } from "../../new_fields/List";
-import { Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../fields/Doc";
+import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../fields/URLField";
+import { HtmlField } from "../../fields/HtmlField";
+import { List } from "../../fields/List";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
import { DocServer } from "../DocServer";
import { dropActionType } from "../util/DragManager";
-import { DateField } from "../../new_fields/DateField";
+import { DateField } from "../../fields/DateField";
import { YoutubeBox } from "../apis/youtube/YoutubeBox";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { LinkManager } from "../util/LinkManager";
@@ -26,10 +26,10 @@ import { Scripting } from "../util/Scripting";
import { LabelBox } from "../views/nodes/LabelBox";
import { SliderBox } from "../views/nodes/SliderBox";
import { FontIconBox } from "../views/nodes/FontIconBox";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { PresBox } from "../views/nodes/PresBox";
-import { ComputedField, ScriptField } from "../../new_fields/ScriptField";
-import { ProxyField } from "../../new_fields/Proxy";
+import { ComputedField, ScriptField } from "../../fields/ScriptField";
+import { ProxyField } from "../../fields/Proxy";
import { DocumentType } from "./DocumentTypes";
import { RecommendationsBox } from "../views/RecommendationsBox";
import { PresElementBox } from "../views/presentationview/PresElementBox";
@@ -37,11 +37,11 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { QueryBox } from "../views/nodes/QueryBox";
import { ColorBox } from "../views/nodes/ColorBox";
import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox";
-import { DocHolderBox } from "../views/nodes/DocumentBox";
+import { DocHolderBox } from "../views/nodes/DocHolderBox";
import { InkingStroke } from "../views/InkingStroke";
-import { InkField } from "../../new_fields/InkField";
+import { InkField } from "../../fields/InkField";
import { InkingControl } from "../views/InkingControl";
-import { RichTextField } from "../../new_fields/RichTextField";
+import { RichTextField } from "../../fields/RichTextField";
import { extname } from "path";
import { MessageStore } from "../../server/Message";
import { ContextMenuProps } from "../views/ContextMenuItem";
@@ -58,6 +58,8 @@ export interface DocumentOptions {
_height?: number;
_nativeWidth?: number;
_nativeHeight?: number;
+ _dimMagnitude?: number; // magnitude of collectionMulti{row,col} view element
+ _dimUnit?: string; // "px" or "*" (default = "*")
_fitWidth?: boolean;
_fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
_LODdisable?: boolean;
@@ -75,12 +77,15 @@ export interface DocumentOptions {
_itemIndex?: number; // which item index the carousel viewer is showing
_showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts
_singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document)
+ "_carousel-caption-xMargin"?: number;
+ "_carousel-caption-yMargin"?: number;
x?: number;
y?: number;
z?: number;
author?: string;
dropAction?: dropActionType;
childDropAction?: dropActionType;
+ targetDropAction?: dropActionType;
layoutKey?: string;
type?: string;
title?: string;
@@ -90,12 +95,16 @@ export interface DocumentOptions {
scale?: number;
isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
forceActive?: boolean;
- layout?: string | Doc;
+ layout?: string | Doc; // default layout string for a document
+ childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
+ childLayoutString?: string; // template string for collection to use to render its children
hideFilterView?: boolean; // whether to hide the filter popout on collections
hideHeadings?: boolean; // whether stacking view column headings should be hidden
isTemplateForField?: string; // the field key for which the containing document is a rendering template
isTemplateDoc?: boolean;
+ targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
templates?: List<string>;
+ hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images)
backgroundColor?: string | ScriptField; // background color for data doc
_backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor )
color?: string; // foreground color data doc
@@ -117,7 +126,9 @@ export interface DocumentOptions {
displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
borderRounding?: string;
boxShadow?: string;
- dontRegisterChildren?: boolean;
+ dontRegisterChildViews?: boolean;
+ lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox.
+ "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
"onClick-rawScript"?: string; // onClick script in raw text form
"onCheckedClick-rawScript"?: string; // onChecked script in raw text form
"onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions
@@ -131,13 +142,16 @@ export interface DocumentOptions {
ischecked?: ScriptField; // returns whether a font icon box is checked
activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
onClick?: ScriptField;
+ onDoubleClick?: ScriptField;
onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
+ onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked
onPointerDown?: ScriptField;
onPointerUp?: ScriptField;
dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
clipboard?: Doc;
+ UseCors?: boolean;
icon?: string;
sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
@@ -377,6 +391,15 @@ 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;
@@ -389,9 +412,13 @@ export namespace Docs {
});
const parentProto = Doc.GetProto(parent);
const { _socket } = DocServer;
+
+ // just in case, clean up
_socket.off(MessageStore.BuxtonDocumentResult.Message);
_socket.off(MessageStore.BuxtonImportComplete.Message);
- Utils.AddServerHandler(_socket, MessageStore.BuxtonDocumentResult, ({ device, errors }) => {
+
+ // 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>();
@@ -406,10 +433,10 @@ export namespace Docs {
_nativeWidth: nativeWidth,
_nativeHeight: nativeHeight
}));
- const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true });
- const deviceProto = Doc.GetProto(doc);
- deviceProto.hero = new ImageField(constructed[0].url);
- Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: deviceProto } });
+ // the main document we create
+ const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true, hero: new ImageField(constructed[0].url) });
+ // add the parsed attributes to this main document
+ Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
Doc.AddDocToList(parentProto, "data", doc);
} else if (errors) {
console.log(errors);
@@ -417,22 +444,22 @@ export namespace Docs {
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, "");
- return parent;
+ Utils.Emit(_socket, MessageStore.BeginBuxtonImport, ""); // signal the server to start importing
+ return parent; // synchronously return the collection, to be populateds
}
Scripting.addGlobal(Buxton);
- const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
- "_chromeStatus", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
- "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", "_scrollTop",
- "_color", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"];
+ const delegateKeys = ["x", "y", "layoutKey", "dropAction", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"];
/**
* This function receives the relevant document prototype and uses
@@ -453,7 +480,7 @@ export namespace Docs {
* main document.
*/
export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") {
- const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
+ const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys, "^_");
if (!("author" in protoProps)) {
protoProps.author = Doc.CurrentUserEmail;
@@ -468,7 +495,7 @@ export namespace Docs {
const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
- viewDoc.type !== DocumentType.LINK && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "audio timeline"));
+ viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
return Doc.assign(viewDoc, delegateProps, true);
}
@@ -541,8 +568,23 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COLOR), "", options);
}
- export function TextDocument(text: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.RTF), text, options, undefined, "text");
+ export function TextDocument(text: string, options: DocumentOptions = {}, fieldKey: string = "text") {
+ const rtf = {
+ doc: {
+ type: "doc", content: [{
+ type: "paragraph",
+ content: [{
+ type: "text",
+ text
+ }]
+ }]
+ },
+ selection: { type: "text", anchor: 1, head: 1 },
+ storedMarks: []
+ };
+
+ const field = text ? new RichTextField(JSON.stringify(rtf), text) : undefined;
+ return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
@@ -553,9 +595,7 @@ export namespace Docs {
linkDocProto.anchor1_timecode = source.doc.currentTimecode || source.doc.displayTimecode;
linkDocProto.anchor2_timecode = target.doc.currentTimecode || target.doc.displayTimecode;
- if (linkDocProto.layout_key1 === undefined) {
- Cast(linkDocProto.proto, Doc, null).layout_key1 = LinkAnchorBox.LayoutString("anchor1");
- Cast(linkDocProto.proto, Doc, null).layout_key2 = LinkAnchorBox.LayoutString("anchor2");
+ if (linkDocProto.linkBoxExcludedKeys === undefined) {
Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
Cast(linkDocProto.proto, Doc, null).layoutKey = undefined;
}
@@ -567,7 +607,7 @@ export namespace Docs {
return doc;
}
- export function InkDocument(color: string, tool: number, strokeWidth: number, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
+ export function InkDocument(color: string, tool: number, strokeWidth: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
const I = new Doc();
I.type = DocumentType.INK;
I.layout = InkingStroke.LayoutString("data");
@@ -607,7 +647,7 @@ export namespace Docs {
}
export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", ...options });
+ return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options });
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -713,6 +753,10 @@ export namespace Docs {
};
return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id);
}
+
+ export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) {
+ return InstanceFromProto(proto, undefined, options);
+ }
}
export namespace Get {
@@ -968,6 +1012,11 @@ export namespace DocUtils {
});
}
+ export let ActiveRecordings: Doc[] = [];
+
+ export function MakeLinkToActiveAudio(doc: Doc) {
+ DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
+ }
export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
const sv = DocumentManager.Instance.getDocumentView(source.doc);
if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
@@ -1019,4 +1068,5 @@ export namespace DocUtils {
}
Scripting.addGlobal("Docs", Docs);
+Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/client/util/CurrentUserUtils.ts
index 4b2aafac1..1f25ed790 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,26 +1,27 @@
-import { action, computed, observable, reaction } from "mobx";
+import { computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
-import { DocServer } from "../../../client/DocServer";
-import { Docs, DocumentOptions } from "../../../client/documents/Documents";
-import { UndoManager } from "../../../client/util/UndoManager";
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, PromiseValue, StrCast } from "../../../new_fields/Types";
-import { Utils } from "../../../Utils";
-import { nullAudio, ImageField } from "../../../new_fields/URLField";
-import { DragManager } from "../../../client/util/DragManager";
-import { InkingControl } from "../../../client/views/InkingControl";
-import { Scripting, CompileScript } from "../../../client/util/Scripting";
-import { CollectionViewType } from "../../../client/views/collections/CollectionView";
-import { makeTemplate } from "../../../client/util/DropConverter";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { PrefetchProxy } from "../../../new_fields/Proxy";
-import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox";
-import { MainView } from "../../../client/views/MainView";
-import { DocumentType } from "../../../client/documents/DocumentTypes";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { Utils } from "../../Utils";
+import { DocServer } from "../DocServer";
+import { Docs, DocumentOptions } from "../documents/Documents";
+import { UndoManager } from "./UndoManager";
+import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { listSpec } from "../../fields/Schema";
+import { ScriptField, ComputedField } from "../../fields/ScriptField";
+import { Cast, PromiseValue, StrCast, NumCast } from "../../fields/Types";
+import { nullAudio } from "../../fields/URLField";
+import { DragManager } from "./DragManager";
+import { InkingControl } from "../views/InkingControl";
+import { Scripting } from "./Scripting";
+import { CollectionViewType } from "../views/collections/CollectionView";
+import { makeTemplate } from "./DropConverter";
+import { RichTextField } from "../../fields/RichTextField";
+import { PrefetchProxy } from "../../fields/Proxy";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
+import { MainView } from "../views/MainView";
+import { DocumentType } from "../documents/DocumentTypes";
+import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
+import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
export class CurrentUserUtils {
private static curr_id: string;
@@ -72,24 +73,64 @@ export class CurrentUserUtils {
}
if (doc["template-button-description"] === undefined) {
- const descriptionTemplate = Docs.Create.TextDocument("", { title: "header", _height: 100 });
- Doc.GetProto(descriptionTemplate).layout = "<div><FormattedTextBox {...props} background='orange' height='50px' fieldKey={'header'}/><FormattedTextBox {...props} height='calc(100% - 50px)' fieldKey={'text'}/></div>";
+ const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created
+ Doc.GetProto(descriptionTemplate).layout =
+ "<div>" +
+ " <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
+ " <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
+ "</div>";
descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView");
doc["template-button-description"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize"
});
}
+ if (doc["template-button-switch"] === undefined) {
+ const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create;
+
+ const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _LODdisable: true, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40 });
+ const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1 });
+ const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, _LODdisable: true });
+ const labelTemplate = {
+ doc: {
+ type: "doc", content: [{
+ type: "paragraph",
+ content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }]
+ }]
+ },
+ selection: { type: "text", anchor: 1, head: 1 },
+ storedMarks: []
+ };
+ Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS");
+ Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'");
+ // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'");
+ // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true");
+ Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]");
+ // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false");
+ const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, });
+ box.isTemplateDoc = makeTemplate(box, true, "switch");
+
+ doc["template-button-switch"] = CurrentUserUtils.ficon({
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ dragFactory: new PrefetchProxy(box) as any as Doc,
+ removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on"
+ });
+ }
+
if (doc["template-button-detail"] === undefined) {
const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create;
- const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" });
+ const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)");
+ const carousel = CarouselDocument([], {
+ title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10,
+ onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F"
+ });
const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true });
- const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 50, _autoHeight: true });
+ const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true });
const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true });
const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"];
@@ -111,8 +152,9 @@ export class CurrentUserUtils {
const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
descriptionWrapper.sectionHeaders = new List<SchemaHeaderField>([
- new SchemaHeaderField("[Long Description]", "LemonChiffon", undefined, undefined, undefined, true),
- new SchemaHeaderField("[Details]", "lightBlue", undefined, undefined, undefined, true),
+ new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false),
+ new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true),
+ new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true),
]);
const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts });
detailView.isTemplateDoc = makeTemplate(detailView);
@@ -130,13 +172,19 @@ export class CurrentUserUtils {
if (doc["template-buttons"] === undefined) {
doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
- doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc], {
- title: "Compound Item Creators", _xMargin: 0, _showTitle: "title",
+ doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc], {
+ title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
_autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
}));
} else {
- DocListCast(Cast(doc["template-buttons"], Doc, null)?.data); // prefetch templates
+ const curButnTypes = Cast(doc["template-buttons"], Doc, null);
+ const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
+ doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc];
+ DocListCastAsync(curButnTypes.data).then(async curBtns => {
+ await Promise.all(curBtns!);
+ requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
+ });
}
return doc["template-buttons"] as Doc;
}
@@ -210,28 +258,29 @@ export class CurrentUserUtils {
static setupDefaultIconTemplates(doc: Doc) {
if (doc["template-icon-view"] === undefined) {
const iconView = Docs.Create.TextDocument("", {
- title: "icon", _width: 150, _height: 30, isTemplateDoc: true,
- onClick: ScriptField.MakeScript("deiconifyView(self)")
+ title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
});
Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
iconView.isTemplateDoc = makeTemplate(iconView);
doc["template-icon-view"] = new PrefetchProxy(iconView);
}
if (doc["template-icon-view-rtf"] === undefined) {
- const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+ const iconRtfView = Docs.Create.LabelDocument({
+ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset",
+ _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+ });
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
}
if (doc["template-icon-view-img"] === undefined) {
const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
- title: "data", _width: 50, isTemplateDoc: true,
- onClick: ScriptField.MakeScript("deiconifyView(self)")
+ title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
});
iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
}
if (doc["template-icon-view-col"] === undefined) {
- const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+ const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") });
iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
}
@@ -240,8 +289,12 @@ export class CurrentUserUtils {
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 }));
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
- DocListCastAsync(templateIconsDoc).then(list => templateIconsDoc.data = new List<Doc>([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]));
+ const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+ doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
+ DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
+ await Promise.all(curIcons!);
+ requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
+ });
}
return doc["template-icons"] as Doc;
}
@@ -252,15 +305,23 @@ export class CurrentUserUtils {
}[] {
if (doc.emptyPresentation === undefined) {
doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Presentation", _viewType: CollectionViewType.Stacking, _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
+ { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
}
if (doc.emptyCollection === undefined) {
doc.emptyCollection = Docs.Create.FreeformDocument([],
{ _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" });
}
+ if (doc.emptyDocHolder === undefined) {
+ doc.emptyDocHolder = Docs.Create.DocumentDocument(
+ ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any,
+ { _width: 250, _height: 250, title: "container" });
+ }
+ if (doc.emptyWebpage === undefined) {
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true });
+ }
return [
{ title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
- { title: "Drag a web page", label: "Web", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("", { title: "New Webpage" })' },
+ { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
{ title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
{ title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
{ title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
@@ -277,8 +338,9 @@ export class CurrentUserUtils {
// { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
// { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc },
// { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc },
- { title: "Drag a document previewer", label: "Prev", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
- { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
+ { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
+ { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
+ { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
];
}
@@ -294,21 +356,22 @@ export class CurrentUserUtils {
alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title));
}
}
- const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)).
- map(data => Docs.Create.FontIconDocument({
- _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
- icon: data.icon,
- title: data.title,
- label: data.label,
- ignoreClick: data.ignoreClick,
- dropAction: data.click ? "copy" : undefined,
- onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
- onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined,
- activePen: data.activePen,
- backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]),
- dragFactory: data.dragFactory,
- }));
+ const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
+ const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
+ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
+ icon,
+ title,
+ label,
+ ignoreClick,
+ dropAction: "copy",
+ onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
+ onClick: click ? ScriptField.MakeScript(click) : undefined,
+ ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined,
+ activePen,
+ backgroundColor,
+ removeDropProperties: new List<string>(["dropAction"]),
+ dragFactory,
+ }));
if (dragCreatorSet === undefined) {
doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
@@ -446,13 +509,13 @@ export class CurrentUserUtils {
return doc.myWorkspaces as Doc;
}
- static setupDocumentCollection(doc: Doc) {
- if (doc.myDocuments === undefined) {
- doc.myDocuments = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true,
+ static setupCatalog(doc: Doc) {
+ if (doc.myCatalog === undefined) {
+ doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
}));
}
- return doc.myDocuments as Doc;
+ return doc.myCatalog as Doc;
}
static setupRecentlyClosed(doc: Doc) {
// setup Recently Closed library item
@@ -472,7 +535,7 @@ export class CurrentUserUtils {
// setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views
static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) {
const workspaces = CurrentUserUtils.setupWorkspaces(doc);
- const documents = CurrentUserUtils.setupDocumentCollection(doc);
+ const documents = CurrentUserUtils.setupCatalog(doc);
const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc);
if (doc["tabs-button-library"] === undefined) {
@@ -480,7 +543,7 @@ export class CurrentUserUtils {
_width: 50, _height: 25, title: "Library", _fontSize: 10,
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
- title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
+ title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
})) as any as Doc,
targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
@@ -548,7 +611,7 @@ export class CurrentUserUtils {
static setupDockedButtons(doc: Doc) {
if (doc["dockedBtn-pen"] === undefined) {
doc["dockedBtn-pen"] = CurrentUserUtils.ficon({
- onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)"),
+ onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this, this.inkWidth, this.backgroundColor)"),
author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activePen.inkPen, this)`), activePen: doc
});
}
@@ -571,14 +634,9 @@ export class CurrentUserUtils {
// the initial presentation Doc to use
static setupDefaultPresentation(doc: Doc) {
- if (doc["template-presentation"] === undefined) {
- doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
- title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data"
- }));
- }
if (doc.activePresentation === undefined) {
doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), {
- title: "Presentation", _viewType: CollectionViewType.Stacking,
+ title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias",
_LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0"
});
}
@@ -591,25 +649,37 @@ export class CurrentUserUtils {
}
static setupClickEditorTemplates(doc: Doc) {
- if (doc.childClickFuncs === undefined) {
+ if (doc["clickFuncs-child"] === undefined) {
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
- "docCast(thisContainer.target).then((target) => { target && docCast(this.source).then((source) => { target.proto.data = new List([source || this]); } ); } )",
- { target: Doc.name }), { title: "On Child Clicked (open in target)", _width: 300, _height: 200 });
+ "docCast(thisContainer.target).then((target) => {" +
+ " target && docCast(this.source).then((source) => { " +
+ " target.proto.data = new List([source || this]); " +
+ " }); " +
+ "})",
+ { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" });
+
+ const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
+ "openOnRight(self.doubleClickView)",
+ { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
- doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget], { title: "on Child Click function templates" });
+ doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
}
// this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
- PromiseValue(Cast(doc.childClickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
+ PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
if (doc.clickFuncs === undefined) {
const onClick = Docs.Create.ScriptingDocument(undefined, {
title: "onClick", "onClick-rawScript": "console.log('click')",
isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200
}, "onClick");
+ const onDoubleClick = Docs.Create.ScriptingDocument(undefined, {
+ title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')",
+ isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200
+ }, "onDoubleClick");
const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200
}, "onCheckedClick");
- doc.clickFuncs = Docs.Create.TreeDocument([onClick, onCheckedClick], { title: "onClick funcs" });
+ doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" });
}
PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
@@ -620,6 +690,11 @@ export class CurrentUserUtils {
new InkingControl();
doc.title = Doc.CurrentUserEmail;
doc.activePen = doc;
+ doc.inkColor = StrCast(doc.backgroundColor, "rgb(0, 0, 0)");
+ doc.fontSize = NumCast(doc.fontSize, 12);
+ doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
+ doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
+ Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index b3295ece0..e46225b4a 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -3,15 +3,15 @@ import { DocumentView } from "../views/nodes/DocumentView";
import { UndoManager } from "./UndoManager";
import * as interpreter from "words-to-numbers";
import { DocumentType } from "../documents/DocumentTypes";
-import { Doc, Opt } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
+import { Doc, Opt } from "../../fields/Doc";
+import { List } from "../../fields/List";
import { Docs } from "../documents/Documents";
import { CollectionViewType } from "../views/collections/CollectionView";
-import { Cast, CastCtor } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
-import { AudioField, ImageField } from "../../new_fields/URLField";
+import { Cast, CastCtor } from "../../fields/Types";
+import { listSpec } from "../../fields/Schema";
+import { AudioField, ImageField } from "../../fields/URLField";
import { Utils } from "../../Utils";
-import { RichTextField } from "../../new_fields/RichTextField";
+import { RichTextField } from "../../fields/RichTextField";
import { DictationOverlay } from "../views/DictationOverlay";
/**
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4683e77a8..ab087335e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
import { action, computed, observable } from 'mobx';
-import { Doc, DocListCastAsync, DocListCast, Opt } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/FieldSymbols';
-import { Cast, NumCast, StrCast } from '../../new_fields/Types';
+import { Doc, DocListCastAsync, DocListCast, Opt } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionView } from '../views/collections/CollectionView';
import { DocumentView, DocFocusFunc } from '../views/nodes/DocumentView';
@@ -94,37 +94,29 @@ export class DocumentManager {
// heuristic to return the "best" documents first:
// choose an exact match over an alias match
// choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth)
- docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view));
- docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view));
- docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
- docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+ docViews.map(view => view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view));
+ docViews.map(view => view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view));
+ docViews.map(view => view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+ docViews.map(view => view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
return toReturn;
}
@computed
public get LinkedDocumentViews() {
- const pairs = DocumentManager.Instance.DocumentViews
- //.filter(dv => (dv.isSelected() || Doc.IsBrushed(dv.props.Document))) // draw links from DocumentViews that are selected or brushed OR
- // || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which
- // const rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors
- // const init = (dv2.isSelected() || Doc.IsBrushed(dv2.props.Document)) && dv2.Document.type !== DocumentType.AUDIO; // on a view that is selected or brushed
- // return init && rest;
- // }
- // )
- .reduce((pairs, dv) => {
- const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
- pairs.push(...linksList.reduce((pairs, link) => {
- const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
- linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
- if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) {
- pairs.push({ a: dv, b: docView1, l: link });
- }
- });
- return pairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
+ const pairs = DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
+ const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
+ pairs.push(...linksList.reduce((pairs, link) => {
+ const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
+ linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+ if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== docView1.props.LayoutTemplateString) {
+ pairs.push({ a: dv, b: docView1, l: link });
+ }
+ });
return pairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
return pairs;
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 35694a6bd..2a9c1633a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,23 +1,17 @@
-import { Doc, Field, DocListCast } from "../../new_fields/Doc";
-import { Cast, ScriptCast, StrCast } from "../../new_fields/Types";
+import { action, observable, runInAction } from "mobx";
+import { DateField } from "../../fields/DateField";
+import { Doc, Field, Opt } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { PrefetchProxy } from "../../fields/Proxy";
+import { listSpec } from "../../fields/Schema";
+import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
+import { ScriptField } from "../../fields/ScriptField";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types";
import { emptyFunction } from "../../Utils";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { DocumentManager } from "./DocumentManager";
-import { LinkManager } from "./LinkManager";
-import { SelectionManager } from "./SelectionManager";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
import { Docs, DocUtils } from "../documents/Documents";
-import { ScriptField } from "../../new_fields/ScriptField";
-import { List } from "../../new_fields/List";
-import { PrefetchProxy } from "../../new_fields/Proxy";
-import { listSpec } from "../../new_fields/Schema";
-import { Scripting } from "./Scripting";
-import { convertDropDataToButtons } from "./DropConverter";
-import { AudioBox } from "../views/nodes/AudioBox";
-import { DateField } from "../../new_fields/DateField";
-import { DocumentView } from "../views/nodes/DocumentView";
+import * as globalCssVariables from "../views/globalCssVariables.scss";
import { UndoManager } from "./UndoManager";
+import { SnappingManager } from "./SnappingManager";
export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move
export function SetupDrag(
@@ -53,10 +47,10 @@ export function SetupDrag(
const onItemDown = async (e: React.PointerEvent) => {
if (e.button === 0) {
e.stopPropagation();
- if (e.shiftKey && CollectionDockingView.Instance) {
+ if (e.shiftKey) {
e.persist();
const dragDoc = await docFunc();
- dragDoc && CollectionDockingView.Instance.StartOtherDrag({
+ dragDoc && DragManager.StartWindowDrag?.({
pageX: e.pageX,
pageY: e.pageY,
preventDefault: emptyFunction,
@@ -73,6 +67,7 @@ export function SetupDrag(
export namespace DragManager {
let dragDiv: HTMLDivElement;
+ export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
export function Root() {
const root = document.getElementById("root");
@@ -82,8 +77,8 @@ export namespace DragManager {
return root;
}
export let AbortDrag: () => void = emptyFunction;
- export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
- export type RemoveFunction = (document: Doc) => boolean;
+ export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+ export type RemoveFunction = (document: Doc | Doc[]) => boolean;
export interface DragDropDisposer { (): void; }
export interface DragOptions {
@@ -180,7 +175,8 @@ export namespace DragManager {
export function MakeDropTarget(
element: HTMLElement,
dropFunc: (e: Event, de: DropEvent) => void,
- doc?: Doc
+ doc?: Doc,
+ preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void,
): DragDropDisposer {
if ("canDrop" in element.dataset) {
throw new Error(
@@ -191,9 +187,7 @@ export namespace DragManager {
const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
const preDropHandler = (e: Event) => {
const de = (e as CustomEvent<DropEvent>).detail;
- if (de.complete.docDragData && doc?.targetDropAction) {
- de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
- }
+ preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType);
};
element.addEventListener("dashOnDrop", handler);
doc && element.addEventListener("dashPreDrop", preDropHandler);
@@ -208,7 +202,7 @@ export namespace DragManager {
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
- dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline"));
+ dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(dropDoc);
return dropDoc;
};
const batch = UndoManager.StartBatch("dragging");
@@ -239,40 +233,6 @@ export namespace DragManager {
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
}
- // drag links and drop link targets (aliasing them if needed)
- export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
- const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
-
- if (draggedDocs.length) {
- const moddrag: Doc[] = [];
- for (const draggedDoc of draggedDocs) {
- const doc = await Cast(draggedDoc.annotationOn, Doc);
- if (doc) moddrag.push(doc);
- }
-
- const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
- dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
- docView.props.removeDocument?.(doc);
- addDocument(doc);
- return true;
- };
- const containingView = docView.props.ContainingCollectionView;
- const finishDrag = (e: DragCompleteEvent) =>
- e.docDragData && (e.docDragData.droppedDocuments =
- dragData.draggedDocuments.reduce((droppedDocs, d) => {
- const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
- if (dvs.length) {
- dvs.forEach(dv => droppedDocs.push(dv.props.Document));
- } else {
- droppedDocs.push(Doc.MakeAlias(d));
- }
- return droppedDocs;
- }, [] as Doc[]));
-
- StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
- }
- }
-
// drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption
export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) {
StartDrag(eles, dragData, downX, downY, options);
@@ -292,7 +252,64 @@ export namespace DragManager {
StartDrag([ele], {}, downX, downY);
}
- function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
+ export function SetSnapLines(horizLines: number[], vertLines: number[]) {
+ SnappingManager.setSnapLines(horizLines, vertLines);
+ }
+ export function snapDragAspect(dragPt: number[], snapAspect: number) {
+ let closest = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ let near = dragPt;
+ const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
+ if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
+ const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
+ if (denominator === 0) return undefined; // Lines are parallel
+
+ const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
+ // let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
+ //if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
+
+ // Return a object with the x and y coordinates of the intersection
+ const x = x1 + ua * (x2 - x1);
+ const y = y1 + ua * (y2 - y1);
+ const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y));
+ return { pt: [x, y], dist };
+ };
+ SnappingManager.vertSnapLines().forEach((xCoord, i) => {
+ const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, xCoord, -1, xCoord, 1, dragPt[0], dragPt[1]);
+ if (pt && pt.dist < closest) {
+ closest = pt.dist;
+ near = pt.pt;
+ }
+ });
+ SnappingManager.horizSnapLines().forEach((yCoord, i) => {
+ const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, -1, yCoord, 1, yCoord, dragPt[0], dragPt[1]);
+ if (pt && pt.dist < closest) {
+ closest = pt.dist;
+ near = pt.pt;
+ }
+ });
+ return { thisX: near[0], thisY: near[1] };
+ }
+ // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
+ export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
+ const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
+ if (snapLines.length) {
+ const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
+ const rangePts = [drag - offs[0], drag - offs[1], drag - offs[2]]; // left, mid, right or top, mid, bottom pts to try to snap to snaplines
+ const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest));
+ const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i]));
+ const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2;
+ return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag;
+ }
+ return drag;
+ };
+ return {
+ thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
+ thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines())
+ };
+ }
+ export let docsBeingDragged: Doc[] = [];
+ export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
eles = eles.filter(e => e);
if (!dragDiv) {
dragDiv = document.createElement("div");
@@ -300,19 +317,29 @@ export namespace DragManager {
dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
- SelectionManager.SetIsDragging(true);
+ SnappingManager.SetIsDragging(true);
const scaleXs: number[] = [];
const scaleYs: number[] = [];
const xs: number[] = [];
const ys: number[] = [];
- const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
+ docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
+ const elesCont = {
+ left: Number.MAX_SAFE_INTEGER,
+ top: Number.MAX_SAFE_INTEGER,
+ right: Number.MIN_SAFE_INTEGER,
+ bottom: Number.MIN_SAFE_INTEGER
+ };
const dragElements = eles.map(ele => {
if (!ele.parentNode) dragDiv.appendChild(ele);
const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
const rect = ele.getBoundingClientRect();
const scaleX = rect.width / ele.offsetWidth,
scaleY = rect.height / ele.offsetHeight;
+ elesCont.left = Math.min(rect.left, elesCont.left);
+ elesCont.top = Math.min(rect.top, elesCont.top);
+ elesCont.right = Math.max(rect.right, elesCont.right);
+ elesCont.bottom = Math.max(rect.bottom, elesCont.bottom);
xs.push(rect.left);
ys.push(rect.top);
scaleXs.push(scaleX);
@@ -332,7 +359,7 @@ export namespace DragManager {
dragElement.style.width = `${rect.width / scaleX}px`;
dragElement.style.height = `${rect.height / scaleY}px`;
- if (docs.length) {
+ if (docsBeingDragged.length) {
const pdfBox = dragElement.getElementsByTagName("canvas");
const pdfBoxSrc = ele.getElementsByTagName("canvas");
Array.from(pdfBox).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
@@ -362,31 +389,38 @@ export namespace DragManager {
let lastX = downX;
let lastY = downY;
+ const xFromLeft = downX - elesCont.left;
+ const yFromTop = downY - elesCont.top;
+ const xFromRight = elesCont.right - downX;
+ const yFromBottom = elesCont.bottom - downY;
let alias = "alias";
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
}
- if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
+ if (e.shiftKey && dragData.droppedDocuments.length === 1) {
!dragData.dropAction && (dragData.dropAction = alias);
if (dragData.dropAction === "move") {
dragData.removeDocument?.(dragData.draggedDocuments[0]);
}
AbortDrag();
finishDrag?.(new DragCompleteEvent(true, dragData));
- CollectionDockingView.Instance.StartOtherDrag({
+ DragManager.StartWindowDrag?.({
pageX: e.pageX,
pageY: e.pageY,
preventDefault: emptyFunction,
button: 0
}, dragData.droppedDocuments);
}
+
+ const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
+
alias = "move";
- const moveX = e.pageX - lastX;
- const moveY = e.pageY - lastY;
- lastX = e.pageX;
- lastY = e.pageY;
+ const moveX = thisX - lastX;
+ const moveY = thisY - lastY;
+ lastX = thisX;
+ lastY = thisY;
dragElements.map((dragElement, i) => (dragElement.style.transform =
`translate(${(xs[i] += moveX) + (options?.offsetX || 0)}px, ${(ys[i] += moveY) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
);
@@ -396,21 +430,22 @@ export namespace DragManager {
dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false));
};
- const endDrag = () => {
+ const endDrag = action(() => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- };
+ SnappingManager.clearSnapLines();
+ });
AbortDrag = () => {
hideDragShowOriginalElements();
- SelectionManager.SetIsDragging(false);
+ SnappingManager.SetIsDragging(false);
options?.dragComplete?.(new DragCompleteEvent(true, dragData));
endDrag();
};
const upHandler = (e: PointerEvent) => {
hideDragShowOriginalElements();
- dispatchDrag(eles, e, dragData, options, finishDrag);
- SelectionManager.SetIsDragging(false);
+ dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
+ SnappingManager.SetIsDragging(false);
endDrag();
options?.dragComplete?.(new DragCompleteEvent(false, dragData));
};
@@ -418,7 +453,8 @@ export namespace DragManager {
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any },
+ xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow };
dragEle.style.width = "0";
@@ -432,14 +468,15 @@ export namespace DragManager {
r.ele.style.height = r.h;
r.ele.style.overflow = r.o;
});
+ const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
if (target) {
const complete = new DragCompleteEvent(false, dragData);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashPreDrop", {
bubbles: true,
detail: {
- x: e.x,
- y: e.y,
+ x: thisX,
+ y: thisY,
complete: complete,
shiftKey: e.shiftKey,
altKey: e.altKey,
@@ -453,8 +490,8 @@ export namespace DragManager {
new CustomEvent<DropEvent>("dashOnDrop", {
bubbles: true,
detail: {
- x: e.x,
- y: e.y,
+ x: thisX,
+ y: thisY,
complete: complete,
shiftKey: e.shiftKey,
altKey: e.altKey,
@@ -466,4 +503,3 @@ export namespace DragManager {
}
}
}
-Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); });
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 60a6bbb3c..752c1cfc5 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,12 +1,13 @@
import { DragManager } from "./DragManager";
-import { Doc, DocListCast, Opt } from "../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { DocumentType } from "../documents/DocumentTypes";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { StrCast } from "../../new_fields/Types";
+import { ObjectField } from "../../fields/ObjectField";
+import { StrCast } from "../../fields/Types";
import { Docs } from "../documents/Documents";
-import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
-import { RichTextField } from "../../new_fields/RichTextField";
-import { ImageField } from "../../new_fields/URLField";
+import { ScriptField, ComputedField } from "../../fields/ScriptField";
+import { RichTextField } from "../../fields/RichTextField";
+import { ImageField } from "../../fields/URLField";
+import { Scripting } from "./Scripting";
//
// converts 'doc' into a template that can be used to render other documents.
@@ -68,10 +69,11 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
});
dbox.dragFactory = layoutDoc;
dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
- dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
+ dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
} else if (doc.isButtonBar) {
dbox.ignoreClick = true;
}
data.droppedDocuments[i] = dbox;
});
}
+Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }); \ No newline at end of file
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 2c53d7e52..7b7d4b835 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,4 +1,4 @@
-import { Doc } from "../../new_fields/Doc";
+import { Doc } from "../../fields/Doc";
import { DocServer } from "../DocServer";
import { MainView } from "../views/MainView";
import * as qs from 'query-string';
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 438904688..1e8f07049 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,6 +1,6 @@
import "fs";
import React = require("react");
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
import { action, observable, runInAction, computed, reaction, IReactionDisposer } from "mobx";
import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
import Measure, { ContentRect } from "react-measure";
@@ -12,12 +12,12 @@ import { observer } from "mobx-react";
import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry";
import { Utils } from "../../../Utils";
import { DocumentManager } from "../DocumentManager";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { Cast, BoolCast, NumCast } from "../../../new_fields/Types";
-import { listSpec } from "../../../new_fields/Schema";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { Cast, BoolCast, NumCast } from "../../../fields/Types";
+import { listSpec } from "../../../fields/Schema";
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import "./DirectoryImportBox.scss";
import { Networking } from "../../Network";
import { BatchedArray } from "array-batcher";
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index c8d1530b3..072e5f58a 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -1,9 +1,9 @@
-import { Doc } from "../../../new_fields/Doc";
-import { ImageField } from "../../../new_fields/URLField";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Doc } from "../../../fields/Doc";
+import { ImageField } from "../../../fields/URLField";
+import { Cast, StrCast } from "../../../fields/Types";
import { Docs } from "../../documents/Documents";
import { Networking } from "../../Network";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { Id } from "../../../fields/FieldSymbols";
import { Utils } from "../../../Utils";
export namespace ImageUtils {
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 8e1c50bea..dcb94e2e0 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -5,8 +5,8 @@ import { action, computed } from "mobx";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { library } from '@fortawesome/fontawesome-svg-core';
-import { Doc } from "../../../new_fields/Doc";
-import { StrCast, BoolCast } from "../../../new_fields/Types";
+import { Doc } from "../../../fields/Doc";
+import { StrCast, BoolCast } from "../../../fields/Types";
interface KeyValueProps {
Document: Doc;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index b1f136430..3a5345c80 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -87,15 +87,17 @@ export namespace InteractionUtils {
return myTouches;
}
- export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) {
+ export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string) {
const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
return (
<polyline
points={pts}
style={{
fill: "none",
- stroke: color,
- strokeWidth: width
+ stroke: color ?? "rgb(0, 0, 0)",
+ strokeWidth: parseInt(width),
+ strokeLinejoin: "round",
+ strokeLinecap: "round"
}}
/>
);
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index e236c7f47..8e6ccf098 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,7 +1,7 @@
-import { Doc, DocListCast } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
-import { listSpec } from "../../new_fields/Schema";
-import { Cast, StrCast } from "../../new_fields/Types";
+import { Doc, DocListCast } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { listSpec } from "../../fields/Schema";
+import { Cast, StrCast } from "../../fields/Types";
import { Docs } from "../documents/Documents";
import { Scripting } from "./Scripting";
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 12628273b..ab577315c 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -9,7 +9,7 @@ export { ts };
// @ts-ignore
import * as typescriptlib from '!!raw-loader!./type_decls.d';
-import { Doc, Field } from '../../new_fields/Doc';
+import { Doc, Field } from '../../fields/Doc';
export interface ScriptSucccess {
success: true;
@@ -136,7 +136,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
if (batch) {
batch.end();
}
- onError && onError(error);
+ onError?.(error);
return { success: false, error, result: errorVal };
}
};
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 6501da34a..5679c0a14 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,7 +1,7 @@
import * as rp from 'request-promise';
import { DocServer } from '../DocServer';
-import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/FieldSymbols';
+import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
import { Utils } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index a49977c42..bd743c28e 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,8 +1,8 @@
import { observable, action, runInAction, ObservableMap } from "mobx";
-import { Doc } from "../../new_fields/Doc";
+import { Doc } from "../../fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { computedFn } from "mobx-utils";
-import { List } from "../../new_fields/List";
+import { List } from "../../fields/List";
export namespace SelectionManager {
@@ -11,7 +11,6 @@ export namespace SelectionManager {
@observable IsDragging: boolean = false;
SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
-
@action
SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
@@ -55,6 +54,8 @@ export namespace SelectionManager {
manager.SelectDoc(docView, ctrlPressed);
}
+ export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
+ export function GetIsDragging() { return manager.IsDragging; }
// computed functions, such as used in IsSelected generate errors if they're called outside of a
// reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature
// to avoid unnecessary mobx invalidations when running inside a reaction.
@@ -78,9 +79,6 @@ export namespace SelectionManager {
if (found) manager.SelectDoc(found, false);
}
- export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
- export function GetIsDragging() { return manager.IsDragging; }
-
export function SelectedDocuments(): Array<DocumentView> {
return Array.from(manager.SelectedDocuments.keys());
}
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 1f6b939d3..ad0309fa7 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,5 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from "serializr";
-import { Field } from "../../new_fields/Doc";
+import { Field } from "../../fields/Doc";
import { ClientUtils } from "./ClientUtils";
let serializing = 0;
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index ff0b22381..0e15197c4 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -8,6 +8,8 @@ import { SelectionManager } from "./SelectionManager";
import "./SettingsManager.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Networking } from "../Network";
+import { CurrentUserUtils } from "./CurrentUserUtils";
+import { Utils } from "../../Utils";
library.add(fa.faWindowClose);
@@ -90,6 +92,9 @@ export default class SettingsManager extends React.Component<{}> {
<div className="settings-type">
<button onClick={this.onClick} value="password">reset password</button>
<button onClick={this.onClick} value="data">reset data</button>
+ <button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
+ {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
+ </button>
</div>
{this.settingsContent === "password" ?
<div className="settings-content">
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 3ce6de80d..dc67145fc 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,13 +1,13 @@
import { observable, runInAction, action } from "mobx";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocCastAsync } from "../../new_fields/Doc";
+import { Doc, Opt, DocCastAsync } from "../../fields/Doc";
import { DocServer } from "../DocServer";
-import { Cast, StrCast } from "../../new_fields/Types";
+import { Cast, StrCast } from "../../fields/Types";
import * as RequestPromise from "request-promise";
import { Utils } from "../../Utils";
import "./SharingManager.scss";
-import { Id } from "../../new_fields/FieldSymbols";
+import { Id } from "../../fields/FieldSymbols";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { library } from '@fortawesome/fontawesome-svg-core';
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
new file mode 100644
index 000000000..fc07e8ab4
--- /dev/null
+++ b/src/client/util/SnappingManager.ts
@@ -0,0 +1,29 @@
+import { observable, action, runInAction } from "mobx";
+
+export namespace SnappingManager {
+
+ class Manager {
+ @observable IsDragging: boolean = false;
+ @observable public horizSnapLines: number[] = [];
+ @observable public vertSnapLines: number[] = [];
+ @action public clearSnapLines() {
+ this.vertSnapLines = [];
+ this.horizSnapLines = [];
+ }
+ @action public setSnapLines(horizLines: number[], vertLines: number[]) {
+ this.horizSnapLines = horizLines;
+ this.vertSnapLines = vertLines;
+ }
+ }
+
+ const manager = new Manager();
+
+ export function clearSnapLines() { manager.clearSnapLines(); }
+ export function setSnapLines(horizLines: number[], vertLines: number[]) { manager.setSnapLines(horizLines, vertLines); }
+ export function horizSnapLines() { return manager.horizSnapLines; }
+ export function vertSnapLines() { return manager.vertSnapLines; }
+
+ export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
+ export function GetIsDragging() { return manager.IsDragging; }
+}
+
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0a8f0c9a7..1ba9fcc32 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,17 +1,20 @@
-import { Doc, Opt, DataSym } from '../../new_fields/Doc';
+import { Doc, Opt, DataSym, DocListCast } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
-import { Cast, BoolCast } from '../../new_fields/Types';
-import { listSpec } from '../../new_fields/Schema';
+import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
+import { listSpec } from '../../fields/Schema';
import { InkingControl } from './InkingControl';
-import { InkTool } from '../../new_fields/InkField';
+import { InkTool } from '../../fields/InkField';
import { InteractionUtils } from '../util/InteractionUtils';
+import { List } from '../../fields/List';
+import { DateField } from '../../fields/DateField';
+import { ScriptField } from '../../fields/ScriptField';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
interface DocComponentProps {
Document: Doc;
- LayoutDoc?: () => Opt<Doc>;
+ LayoutTemplate?: () => Opt<Doc>;
}
export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
class Component extends Touchable<P> {
@@ -20,7 +23,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
// This is the "The Document" -- it encapsulates, data, layout, and any templates
@computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+ @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
@@ -33,6 +36,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
interface ViewBoxBaseProps {
Document: Doc;
DataDoc?: Doc;
+ ContainingCollectionDoc: Opt<Doc>;
fieldKey: string;
isSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
@@ -53,6 +57,8 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
// key where data is stored
@computed get fieldKey() { return this.props.fieldKey; }
+ lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
+
active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
@@ -87,29 +93,58 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
// key where data is stored
@computed get fieldKey() { return this.props.fieldKey; }
+ lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
+
+ styleFromLayoutString = (scale: number) => {
+ const style: { [key: string]: any } = {};
+ const divKeys = ["width", "height", "background", "top", "position"];
+ const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result as string || "";
+ };
+ divKeys.map((prop: string) => {
+ const p = (this.props as any)[prop] as string;
+ p && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+ });
+ return style;
+ }
+
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
_annotationKey: string = "annotations";
- public set annotationKey(val: string) { this._annotationKey = val; }
- public get annotationKey() { return this._annotationKey; }
+ public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
@action.bound
- removeDocument(doc: Doc): boolean {
- Doc.GetProto(doc).annotationOn = undefined;
- const value = Cast(this.dataDoc[this.props.fieldKey + "-" + this._annotationKey], listSpec(Doc), []);
- const index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
- return index !== -1 && value && value.splice(index, 1) ? true : false;
+ removeDocument(doc: Doc | Doc[]): boolean {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ docs.map(doc => doc.annotationOn = undefined);
+ const targetDataDoc = this.dataDoc;
+ const value = DocListCast(targetDataDoc[this.annotationKey]);
+ const result = value.filter(v => !docs.includes(v));
+ if (result.length !== value.length) {
+ targetDataDoc[this.annotationKey] = new List<Doc>(result);
+ return true;
+ }
+ return false;
}
// if the moved document is already in this overlay collection nothing needs to be done.
// otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection.
@action.bound
- moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
+ moveDocument(doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean {
return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false;
}
@action.bound
- addDocument(doc: Doc): boolean {
- doc.context = Doc.GetProto(doc).annotationOn = this.props.Document;
- return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false;
+ addDocument(doc: Doc | Doc[]): boolean {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ docs.map(doc => doc.context = Doc.GetProto(doc).annotationOn = this.props.Document);
+ const targetDataDoc = this.props.Document[DataSym];
+ const docList = DocListCast(targetDataDoc[this.annotationKey]);
+ const added = docs.filter(d => !docList.includes(d));
+ if (added.length) {
+ added.map(doc => doc.context = this.props.Document);
+ targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ return true;
}
whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 3624cdb6d..2db5cd3ba 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,11 +1,11 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faArrowAltCircleRight, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../new_fields/Doc";
-import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast, StrCast } from "../../new_fields/Types";
+import { Doc, DocListCast } from "../../fields/Doc";
+import { RichTextField } from '../../fields/RichTextField';
+import { NumCast, StrCast, Cast } from "../../fields/Types";
import { emptyFunction, setupMoveUpEvents } from "../../Utils";
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
import { UndoManager } from "../util/UndoManager";
@@ -22,6 +22,7 @@ import React = require("react");
import { DragManager } from '../util/DragManager';
import { MetadataEntryMenu } from './MetadataEntryMenu';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { Docs } from '../documents/Documents';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -31,6 +32,7 @@ library.add(faTag);
library.add(faTimes);
library.add(faArrowAltCircleDown);
library.add(faArrowAltCircleUp);
+library.add(faArrowAltCircleRight);
library.add(faStopCircle);
library.add(faCheckCircle);
library.add(faCloudUploadAlt);
@@ -41,12 +43,16 @@ library.add(faPhotoVideo);
const cloud: IconProp = "cloud-upload-alt";
const fetch: IconProp = "sync-alt";
+enum UtilityButtonState {
+ Default,
+ OpenRight,
+ OpenExternally
+}
+
@observer
-export class DocumentButtonBar extends React.Component<{ views: (DocumentView | undefined)[], stack?: any }, {}> {
+export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
private _dragRef = React.createRef<HTMLDivElement>();
- private _downX = 0;
- private _downY = 0;
private _pullAnimating = false;
private _pushAnimating = false;
private _pullColorAnimating = false;
@@ -57,13 +63,13 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
@observable public isAnimatingFetch = false;
@observable public isAnimatingPulse = false;
- @observable private openHover = false;
+ @observable private openHover: UtilityButtonState = UtilityButtonState.Default;
@observable public static Instance: DocumentButtonBar;
public static hasPushedHack = false;
public static hasPulledHack = false;
- constructor(props: { views: (DocumentView | undefined)[] }) {
+ constructor(props: { views: () => (DocumentView | undefined)[] }) {
super(props);
runInAction(() => DocumentButtonBar.Instance = this);
}
@@ -105,7 +111,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
this._pullColorAnimating = false;
});
- get view0() { return this.props.views?.[0]; }
+ get view0() { return this.props.views()?.[0]; }
@action
onLinkButtonMoved = (e: PointerEvent) => {
@@ -165,14 +171,37 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
const dataDoc = targetDoc && Doc.GetProto(targetDoc);
const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <div className="documentButtonBar-linker"
- title={`${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`}
+ title={(() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default: return `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
+ case UtilityButtonState.OpenRight: return "Open in Right Split";
+ case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
+ }
+ })()}
style={{ backgroundColor: this.pullColor }}
- onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
- onPointerLeave={action(() => this.openHover = false)}
- onClick={e => {
+ onPointerEnter={action(e => {
if (e.altKey) {
+ this.openHover = UtilityButtonState.OpenExternally;
+ } else if (e.shiftKey) {
+ this.openHover = UtilityButtonState.OpenRight;
+ }
+ })}
+ onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
+ onClick={async e => {
+ const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
+ if (e.shiftKey) {
e.preventDefault();
- window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
+ let googleDoc = await Cast(dataDoc.googleDoc, Doc);
+ if (!googleDoc) {
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
+ googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
+ dataDoc.googleDoc = googleDoc;
+ }
+ CollectionDockingView.AddRightSplit(googleDoc);
+ } else if (e.altKey) {
+ e.preventDefault();
+ window.open(googleDocUrl);
} else {
this.clearPullColor();
DocumentButtonBar.hasPulledHack = false;
@@ -182,7 +211,14 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
}}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm"
style={{ WebkitAnimation: animation, MozAnimation: animation }}
- icon={this.openHover ? "share" : dataDoc.unchanged === false ? (this.pullIcon as any) : fetch}
+ icon={(() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
+ case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
+ case UtilityButtonState.OpenExternally: return "share";
+ }
+ })()}
/>
</div>;
}
@@ -193,16 +229,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
return !targetDoc ? (null) : <div className="documentButtonBar-linker"
title={Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}
style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
-
- onClick={e => {
- if (isPinned) {
- DockedFrameRenderer.UnpinDoc(targetDoc);
- }
- else {
- targetDoc.sourceContext = this.view0?.props.ContainingCollectionDoc; // bcz: !! Shouldn't need this ... use search to lookup contexts dynamically
- DockedFrameRenderer.PinDoc(targetDoc);
- }
- }}>
+ onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
/>
</div>;
@@ -227,7 +254,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
const view0 = this.view0;
return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout">
<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? */}>
+ 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" />}
</div>
@@ -251,7 +278,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
}
onAliasButtonMoved = () => {
if (this._dragRef.current) {
- const dragDocView = this.props.views[0]!;
+ const dragDocView = this.view0!;
const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
dragData.embedDoc = true;
@@ -270,16 +297,18 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
get templateButton() {
const view0 = this.view0;
const templates: Map<Template, boolean> = new Map();
+ const views = this.props.views();
Array.from(Object.values(Templates.TemplateList)).map(template =>
- templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
- return !view0 ? (null) : <div title="Tap: Customize layout. Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
- content={!this._aliasDown ? (null) : <TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
- <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
- </div>
- </Flyout>
- </div>;
+ templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
+ return !view0 ? (null) :
+ <div title="Tap: Customize layout. Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
+ content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+ <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
+ </div>
+ </Flyout>
+ </div>;
}
render() {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 28cf9fd47..a4d4af2f0 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -21,9 +21,13 @@ $linkGap : 3px;
background: none;
}
+
.documentDecorations-resizer {
pointer-events: auto;
background: $alt-accent;
+ opacity: 0.1;
+ }
+ .documentDecorations-resizer:hover {
opacity: 1;
}
@@ -40,7 +44,6 @@ $linkGap : 3px;
.documentDecorations-radius {
pointer-events: auto;
- background: black;
opacity: 1;
transform: translate(10px, 10px);
grid-row: 4;
@@ -69,6 +72,7 @@ $linkGap : 3px;
grid-column-start: 5;
grid-column-end: 7;
border-radius: 100%;
+ background: dimgray;
.borderRadiusTooltip {
width: 10px;
@@ -80,7 +84,20 @@ $linkGap : 3px;
#documentDecorations-topLeftResizer,
#documentDecorations-bottomRightResizer {
cursor: nwse-resize;
- background: dimGray;
+ background: unset;
+ opacity: 1;
+ }
+ #documentDecorations-topLeftResizer {
+ border-left: 2px solid;
+ border-top: solid 2px;
+ }
+ #documentDecorations-bottomRightResizer {
+ border-right: 2px solid;
+ border-bottom: solid 2px;
+ }
+ #documentDecorations-topLeftResizer:hover,
+ #documentDecorations-bottomRightResizer:hover {
+ opacity: 1;
}
#documentDecorations-bottomRightResizer {
@@ -90,7 +107,22 @@ $linkGap : 3px;
#documentDecorations-topRightResizer,
#documentDecorations-bottomLeftResizer {
cursor: nesw-resize;
+ background: unset;
+ opacity: 1;
+ }
+ #documentDecorations-topRightResizer {
+ border-right: 2px solid;
+ border-top: 2px solid;
+ }
+ #documentDecorations-bottomLeftResizer {
+ border-left: 2px solid;
+ border-bottom: 2px solid;
+ }
+ #documentDecorations-topRightResizer:hover,
+ #documentDecorations-bottomLeftResizer:hover {
+ cursor: nesw-resize;
background: dimGray;
+ opacity: 1;
}
#documentDecorations-topResizer,
@@ -104,22 +136,22 @@ $linkGap : 3px;
}
.documentDecorations-contextMenu {
- background: $alt-accent;
width: 25px;
height: calc(100% + 8px); // 8px for the height of the top resizer bar
- grid-column-start: 1;
+ grid-column-start: 2;
grid-column-end : 2;
pointer-events: all;
+ padding-left: 5px;
}
.documentDecorations-title {
- background: $alt-accent;
opacity: 1;
grid-column-start: 3;
grid-column-end: 4;
pointer-events: auto;
overflow: hidden;
text-align: center;
- display:flex;
+ display: flex;
+ border-bottom: solid 1px;
}
.publishBox {
width: 20px;
@@ -136,7 +168,6 @@ $linkGap : 3px;
.documentDecorations-closeButton {
- background: $alt-accent;
opacity: 1;
grid-column-start: 4;
grid-column-end: 6;
@@ -146,7 +177,6 @@ $linkGap : 3px;
}
.documentDecorations-minimizeButton {
- background: $alt-accent;
opacity: 1;
grid-column-start: 1;
grid-column-end: 3;
@@ -156,9 +186,12 @@ $linkGap : 3px;
position: absolute;
left: 0px;
top: 0px;
- width: $MINIMIZED_ICON_SIZE;
+ width: 8px;
height: $MINIMIZED_ICON_SIZE;
max-height: 20px;
+ > svg {
+ margin:0;
+ }
}
.documentDecorations-background {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 312acd5b2..fc7c16296 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,10 +3,10 @@ import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faT
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DataSym, Field } from "../../new_fields/Doc";
-import { PositionDocument } from '../../new_fields/documentSchemas';
-import { ScriptField } from '../../new_fields/ScriptField';
-import { Cast, StrCast, NumCast } from "../../new_fields/Types";
+import { Doc, DataSym, Field, WidthSym, HeightSym } from "../../fields/Doc";
+import { Document } from '../../fields/documentSchemas';
+import { ScriptField } from '../../fields/ScriptField';
+import { Cast, StrCast, NumCast } from "../../fields/Types";
import { Utils, setupMoveUpEvents, emptyFunction, returnFalse, simulateMouseClick } from "../../Utils";
import { DocUtils } from "../documents/Documents";
import { DocumentType } from '../documents/DocumentTypes';
@@ -17,9 +17,10 @@ import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { DocumentView } from "./nodes/DocumentView";
import React = require("react");
-import { Id } from '../../new_fields/FieldSymbols';
+import { Id } from '../../fields/FieldSymbols';
import e = require('express');
import { CollectionDockingView } from './collections/CollectionDockingView';
+import { SnappingManager } from '../util/SnappingManager';
library.add(faCaretUp);
library.add(faObjectGroup);
@@ -46,6 +47,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _linkBoxHeight = 20 + 3; // link button height + margin
private _titleHeight = 20;
private _resizeUndo?: UndoManager.Batch;
+ private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
+ private _snapX = 0; _snapY = 0; // last snapped location of resize border
@observable private _accumulatedTitle = "";
@observable private _titleControlString: string = "#title";
@observable private _edtingTitle = false;
@@ -170,8 +173,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
@undoBatch
@action
- onCloseClick = async (e: PointerEvent) => {
- if (e.button === 0) {
+ onCloseClick = async (e: PointerEvent | undefined) => {
+ if (!e?.button) {
const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
const selected = SelectionManager.SelectedDocuments().slice();
SelectionManager.DeselectAll();
@@ -233,19 +236,60 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return false;
}
+ _initialAutoHeight = false;
+ _dragHeights = new Map<Doc, number>();
@action
onPointerDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
if (e.button === 0) {
this._resizeHdlId = e.currentTarget.id;
+ const bounds = e.currentTarget.getBoundingClientRect();
+ this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
+ this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
this.Interacting = true;
this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
+ SelectionManager.SelectedDocuments()[0].props.setupDragLines?.();
}
+ this._snapX = e.pageX;
+ this._snapY = e.pageY;
+ this._initialAutoHeight = true;
+ DragManager.docsBeingDragged = SelectionManager.SelectedDocuments().map(dv => dv.rootDoc);
+ SelectionManager.SelectedDocuments().map(dv => {
+ this._dragHeights.set(dv.layoutDoc, NumCast(dv.layoutDoc._height));
+ dv.layoutDoc._delayAutoHeight = dv.layoutDoc._height;
+ });
}
onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
- let dX = 0, dY = 0, dW = 0, dH = 0;
+ const first = SelectionManager.SelectedDocuments()[0];
+ let thisPt = { thisX: e.clientX - this._offX, thisY: e.clientY - this._offY };
+ const fixedAspect = first.layoutDoc._nativeWidth ? NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight) : 0;
+ if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+ const project = (p: number[], a: number[], b: number[]) => {
+ const atob = [b[0] - a[0], b[1] - a[1]];
+ const atop = [p[0] - a[0], p[1] - a[1]];
+ const len = atob[0] * atob[0] + atob[1] * atob[1];
+ let dot = atop[0] * atob[0] + atop[1] * atob[1];
+ const t = dot / len;
+ dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
+ return [a[0] + atob[0] * t, a[1] + atob[1] * t];
+ };
+ const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]);
+ thisPt = DragManager.snapDragAspect(drag, fixedAspect);
+ } else {
+ thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
+ }
+
+ move[0] = thisPt.thisX - this._snapX;
+ move[1] = thisPt.thisY - this._snapY;
+ this._snapX = thisPt.thisX;
+ this._snapY = thisPt.thisY;
+ let dX = 0, dY = 0, dW = 0, dH = 0;
+ const unfreeze = () =>
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
+ (element.rootDoc.type === DocumentType.RTF && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
switch (this._resizeHdlId) {
case "": break;
case "documentDecorations-topLeftResizer":
@@ -260,6 +304,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
dH = -move[1];
break;
case "documentDecorations-topResizer":
+ unfreeze();
dY = -1;
dH = -move[1];
break;
@@ -273,27 +318,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
dH = move[1];
break;
case "documentDecorations-bottomResizer":
+ unfreeze();
dH = move[1];
break;
case "documentDecorations-leftResizer":
+ unfreeze();
dX = -1;
dW = -move[0];
break;
case "documentDecorations-rightResizer":
+ unfreeze();
dW = move[0];
break;
}
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+ if (e.ctrlKey && !element.props.Document._nativeHeight) element.toggleNativeDimensions();
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = PositionDocument(element.props.Document);
- const layoutDoc = PositionDocument(Doc.Layout(element.props.Document));
- let nwidth = layoutDoc._nativeWidth || 0;
- let nheight = layoutDoc._nativeHeight || 0;
- const width = (layoutDoc._width || 0);
- const height = (layoutDoc._height || (nheight / nwidth * width));
+ const doc = Document(element.rootDoc);
+ let nwidth = doc._nativeWidth || 0;
+ let nheight = doc._nativeHeight || 0;
+ const width = (doc._width || 0);
+ let height = (doc._height || (nheight / nwidth * width));
const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
if (nwidth && nheight) {
+ if (nwidth / nheight !== width / height) {
+ height = nheight / nwidth * width;
+ }
if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
else dW = dH * nwidth / nheight;
}
@@ -303,8 +354,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
doc.y = (doc.y || 0) + dY * (actualdH - height);
const fixedAspect = (nwidth && nheight);
if (fixedAspect && (!nwidth || !nheight)) {
- layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0;
- layoutDoc._nativeHeight = nheight = layoutDoc._height || 0;
+ doc._nativeWidth = nwidth = doc._width || 0;
+ doc._nativeHeight = nheight = doc._height || 0;
}
const anno = Cast(doc.annotationOn, Doc, null);
if (e.ctrlKey && anno) {
@@ -320,24 +371,24 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
else if (nwidth > 0 && nheight > 0) {
if (Math.abs(dW) > Math.abs(dH)) {
if (!fixedAspect) {
- layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0);
+ doc._nativeWidth = actualdW / (doc._width || 1) * (doc._nativeWidth || 0);
}
- layoutDoc._width = actualdW;
- if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
- else layoutDoc._height = actualdH;
+ doc._width = actualdW;
+ if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width;
+ else doc._height = actualdH;
}
else {
if (!fixedAspect) {
- layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0);
+ doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0);
}
- layoutDoc._height = actualdH;
- if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
- else layoutDoc._width = actualdW;
+ doc._height = actualdH;
+ if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height;
+ else doc._width = actualdW;
}
} else {
- dW && (layoutDoc._width = actualdW);
- dH && (layoutDoc._height = actualdH);
- dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
+ dW && (doc._width = actualdW);
+ dH && (doc._height = actualdH);
+ dH && this._initialAutoHeight && (doc._autoHeight = this._initialAutoHeight = false);
}
}
}));
@@ -346,10 +397,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@action
onPointerUp = (e: PointerEvent): void => {
+ SelectionManager.SelectedDocuments().map(dv => {
+ if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) {
+ dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.props.ContentScaling(), dv.panelWidth(), dv.panelHeight());
+ dv.layoutDoc._autoHeight = true;
+ }
+ dv.layoutDoc._delayAutoHeight = undefined;
+ });
this._resizeHdlId = "";
this.Interacting = false;
(e.button === 0) && this._resizeUndo?.end();
this._resizeUndo = undefined;
+ SnappingManager.clearSnapLines();
}
@computed
@@ -388,7 +447,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined;
const bounds = this.Bounds;
const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
- if (SelectionManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+ if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return (null);
}
const minimal = bounds.r - bounds.x < 100 ? true : false;
@@ -417,12 +476,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon>
</div>}
</> :
- <div className="documentDecorations-title" onPointerDown={this.onTitleDown} >
- {minimal ? (null) : <div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
+ <>
+ {minimal ? (null) : <div className="documentDecorations-contextMenu" key="menu" title="Show context menu" onPointerDown={this.onSettingsDown}>
<FontAwesomeIcon size="lg" icon="cog" />
</div>}
- <span style={{ width: "calc(100% - 25px)", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
- </div>;
+ <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
+ <span style={{ width: "calc(100% - 25px)", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
+ </div>
+ </>
bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
@@ -473,16 +534,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : <div id="documentDecorations-levelSelector" className="documentDecorations-selector" title="tap to select containing document"
- onPointerDown={this.onSelectorUp} onContextMenu={(e) => e.preventDefault()}>
- <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
- </div>}
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
+ <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+ title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
+ <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+ </div>}
<div id="documentDecorations-borderRadius" className="documentDecorations-radius"
onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
</div >
<div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2, top: bounds.b + this._resizeBorderWidth / 2 }}>
- <DocumentButtonBar views={SelectionManager.SelectedDocuments()} />
+ <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
</div>
</div >
);
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index b750eff3e..4f3da70d7 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -2,8 +2,8 @@ import React = require('react');
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as Autosuggest from 'react-autosuggest';
-import { ObjectField } from '../../new_fields/ObjectField';
-import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField';
+import { ObjectField } from '../../fields/ObjectField';
+import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
import "./EditableView.scss";
import { DragManager } from '../util/DragManager';
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 4f8f9ed69..4352ac52c 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -6,14 +6,14 @@ import { computed, observable, action, runInAction, IReactionDisposer, reaction,
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { InteractionUtils } from "../util/InteractionUtils";
import { InkingControl } from "./InkingControl";
-import { InkTool, InkData } from "../../new_fields/InkField";
-import { Doc } from "../../new_fields/Doc";
+import { InkTool, InkData } from "../../fields/InkField";
+import { Doc } from "../../fields/Doc";
import { LinkManager } from "../util/LinkManager";
import { DocUtils, Docs } from "../documents/Documents";
import { undoBatch } from "../util/UndoManager";
import { Scripting } from "../util/Scripting";
-import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../fields/Types";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import HorizontalPalette from "./Palette";
import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils";
import { DocumentView } from "./nodes/DocumentView";
@@ -22,9 +22,9 @@ import { DocumentContentsView } from "./nodes/DocumentContentsView";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { DocServer } from "../DocServer";
import htmlToImage from "html-to-image";
-import { ScriptField } from "../../new_fields/ScriptField";
-import { listSpec } from "../../new_fields/Schema";
-import { List } from "../../new_fields/List";
+import { ScriptField } from "../../fields/ScriptField";
+import { listSpec } from "../../fields/Schema";
+import { List } from "../../fields/List";
import { CollectionViewType } from "./collections/CollectionView";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
import MobileInterface from "../../mobile/MobileInterface";
@@ -38,10 +38,8 @@ import { SelectionManager } from "../util/SelectionManager";
export default class GestureOverlay extends Touchable {
static Instance: GestureOverlay;
- @observable public Color: string = "rgb(0, 0, 0)";
- @observable public Width: number = 2;
@observable public SavedColor?: string;
- @observable public SavedWidth?: number;
+ @observable public SavedWidth?: string;
@observable public Tool: ToolglassTools = ToolglassTools.None;
@observable private _thumbX?: number;
@@ -711,12 +709,12 @@ export default class GestureOverlay extends Touchable {
this._palette,
[this._strokes.map(l => {
const b = this.getBounds(l);
- return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
- {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
+ return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
+ {InteractionUtils.CreatePolyline(l, b.left, b.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth)}
</svg>;
}),
- this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
- {InteractionUtils.CreatePolyline(this._points, B.left, B.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
+ this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
+ {InteractionUtils.CreatePolyline(this._points, B.left, B.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth)}
</svg>]
];
}
@@ -806,16 +804,16 @@ Scripting.addGlobal(function setToolglass(tool: any) {
});
Scripting.addGlobal(function setPen(width: any, color: any) {
runInAction(() => {
- GestureOverlay.Instance.SavedColor = GestureOverlay.Instance.Color;
- GestureOverlay.Instance.Color = color;
- GestureOverlay.Instance.SavedWidth = GestureOverlay.Instance.Width;
- GestureOverlay.Instance.Width = width;
+ GestureOverlay.Instance.SavedColor = InkingControl.Instance.selectedColor;
+ InkingControl.Instance.updateSelectedColor(color);
+ GestureOverlay.Instance.SavedWidth = InkingControl.Instance.selectedWidth;
+ InkingControl.Instance.switchWidth(width);
});
});
Scripting.addGlobal(function resetPen() {
runInAction(() => {
- GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)";
- GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 2;
+ InkingControl.Instance.updateSelectedColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
+ InkingControl.Instance.switchWidth(GestureOverlay.Instance.SavedWidth ?? "2");
});
});
Scripting.addGlobal(function createText(text: any, x: any, y: any) {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 185222541..255142771 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,17 +1,26 @@
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager, undoBatch } from "../util/UndoManager";
import { SelectionManager } from "../util/SelectionManager";
import { CollectionDockingView } from "./collections/CollectionDockingView";
import { MainView } from "./MainView";
import { DragManager } from "../util/DragManager";
import { action, runInAction } from "mobx";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCast } from "../../fields/Doc";
import { DictationManager } from "../util/DictationManager";
import SharingManager from "../util/SharingManager";
-import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
-import { ScriptField } from "../../new_fields/ScriptField";
+import { Cast, PromiseValue, NumCast } from "../../fields/Types";
+import { ScriptField } from "../../fields/ScriptField";
import { InkingControl } from "./InkingControl";
-import { InkTool } from "../../new_fields/InkField";
+import { InkTool } from "../../fields/InkField";
import { DocumentView } from "./nodes/DocumentView";
+import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
+import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
+import { Id } from "../../fields/FieldSymbols";
+import { DocumentDecorations } from "./DocumentDecorations";
+import { DocumentType } from "../documents/DocumentTypes";
+import { DocServer } from "../DocServer";
+import { List } from "../../fields/List";
+import { DateField } from "../../fields/DateField";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -64,6 +73,9 @@ export default class KeyManager {
private unmodified = action((keyname: string, e: KeyboardEvent) => {
switch (keyname) {
+ case " ":
+ MarqueeView.DragMarquee = !MarqueeView.DragMarquee;
+ break;
case "escape":
const main = MainView.Instance;
InkingControl.Instance.switchTool(InkTool.None);
@@ -79,6 +91,7 @@ export default class KeyManager {
SelectionManager.DeselectAll();
DictationManager.Controls.stop();
// RecommendationsBox.Instance.closeMenu();
+ GoogleAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
break;
case "delete":
@@ -233,11 +246,30 @@ export default class KeyManager {
break;
case "a":
case "v":
- case "x":
- case "c":
stopPropagation = false;
preventDefault = false;
break;
+ case "x":
+ if (SelectionManager.SelectedDocuments().length) {
+ const bds = DocumentDecorations.Instance.Bounds;
+ const pt = [bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2];
+ const text = `__DashDocId(${pt[0]},${pt[1]}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
+ SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);
+ DocumentDecorations.Instance.onCloseClick(undefined);
+ stopPropagation = false;
+ preventDefault = false;
+ }
+ break;
+ case "c":
+ if (SelectionManager.SelectedDocuments().length) {
+ const bds = DocumentDecorations.Instance.Bounds;
+ const pt = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+ const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
+ SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);
+ stopPropagation = false;
+ preventDefault = false;
+ }
+ break;
}
return {
@@ -246,6 +278,36 @@ export default class KeyManager {
};
});
+ public paste(e: ClipboardEvent) {
+ if (e.clipboardData?.getData("text/plain") !== "" && e.clipboardData?.getData("text/plain").startsWith("__DashDocId(")) {
+ const first = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
+ if (first?.props.Document.type === DocumentType.COL) {
+ const docids = e.clipboardData.getData("text/plain").split(":");
+ let count = 1;
+ const list: Doc[] = [];
+ const targetDataDoc = Doc.GetProto(first.props.Document);
+ const fieldKey = Doc.LayoutFieldKey(first.props.Document);
+ const docList = DocListCast(targetDataDoc[fieldKey]);
+ docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => {
+ count++;
+ if (doc instanceof Doc) {
+ list.push(doc);
+ }
+ if (count === docids.length) {
+ const added = list.filter(d => !docList.includes(d));
+ if (added.length) {
+ added.map(doc => doc.context = targetDataDoc);
+ undoBatch(() => {
+ targetDataDoc[fieldKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ })();
+ }
+ }
+ }));
+ }
+ }
+ }
+
async printClipboard() {
const text: string = await navigator.clipboard.readText();
}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 70ea955e1..41ee36d05 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,9 +1,9 @@
import { action, computed, observable } from "mobx";
import { ColorState } from 'react-color';
-import { Doc } from "../../new_fields/Doc";
-import { InkTool } from "../../new_fields/InkField";
-import { FieldValue, NumCast, StrCast } from "../../new_fields/Types";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { Doc } from "../../fields/Doc";
+import { InkTool } from "../../fields/InkField";
+import { FieldValue, NumCast, StrCast } from "../../fields/Types";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { Scripting } from "../util/Scripting";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
@@ -13,8 +13,8 @@ import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
export class InkingControl {
@observable static Instance: InkingControl;
@computed private get _selectedTool(): InkTool { return FieldValue(NumCast(Doc.UserDoc().inkTool)) ?? InkTool.None; }
- @computed private get _selectedColor(): string { return GestureOverlay.Instance.Color ?? FieldValue(StrCast(Doc.UserDoc().inkColor)) ?? "rgb(244, 67, 54)"; }
- @computed private get _selectedWidth(): string { return GestureOverlay.Instance.Width?.toString() ?? FieldValue(StrCast(Doc.UserDoc().inkWidth)) ?? "5"; }
+ @computed private get _selectedColor(): string { return CurrentUserUtils.ActivePen ? FieldValue(StrCast(CurrentUserUtils.ActivePen.backgroundColor)) ?? "rgb(0, 0, 0)" : "rgb(0, 0, 0)"; }
+ @computed private get _selectedWidth(): string { return FieldValue(StrCast(Doc.UserDoc().inkWidth)) ?? "2"; }
@observable public _open: boolean = false;
constructor() {
@@ -34,7 +34,9 @@ export class InkingControl {
@undoBatch
switchColor = action((color: ColorState): void => {
- Doc.UserDoc().inkColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
+ Doc.UserDoc().backgroundColor = color.hex.startsWith("#") ?
+ color.hex + (color.rgb.a ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex;
+ CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = color.hex);
if (InkingControl.Instance.selectedTool === InkTool.None) {
const selected = SelectionManager.SelectedDocuments();
@@ -44,20 +46,20 @@ export class InkingControl {
view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
if (targetDoc) {
if (StrCast(Doc.Layout(view.props.Document).layout).indexOf("FormattedTextBox") !== -1 && FormattedTextBox.HadSelection) {
- Doc.Layout(view.props.Document).color = Doc.UserDoc().inkColor;
+ Doc.Layout(view.props.Document).color = Doc.UserDoc().bacgroundColor;
} else {
- Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().inkColor; // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
+ Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
}
}
});
- } else {
- CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor);
}
});
@action
switchWidth = (width: string): void => {
// this._selectedWidth = width;
- Doc.UserDoc().inkWidth = width;
+ if (!isNaN(parseInt(width))) {
+ Doc.UserDoc().inkWidth = width;
+ }
}
@computed
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7a318d5c2..8938e8b6c 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,14 +1,14 @@
import { observer } from "mobx-react";
-import { documentSchema } from "../../new_fields/documentSchemas";
-import { InkData, InkField, InkTool } from "../../new_fields/InkField";
-import { makeInterface } from "../../new_fields/Schema";
-import { Cast, StrCast, NumCast } from "../../new_fields/Types";
+import { documentSchema } from "../../fields/documentSchemas";
+import { InkData, InkField, InkTool } from "../../fields/InkField";
+import { makeInterface } from "../../fields/Schema";
+import { Cast, StrCast, NumCast } from "../../fields/Types";
import { ViewBoxBaseComponent } from "./DocComponent";
import { InkingControl } from "./InkingControl";
import "./InkingStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
-import { TraceMobx } from "../../new_fields/util";
+import { TraceMobx } from "../../fields/util";
import { InteractionUtils } from "../util/InteractionUtils";
import { ContextMenu } from "./ContextMenu";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
@@ -40,7 +40,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const bottom = Math.max(...ys);
const points = InteractionUtils.CreatePolyline(data, left, top,
StrCast(this.layoutDoc.color, InkingControl.Instance.selectedColor),
- NumCast(this.layoutDoc.strokeWidth, parseInt(InkingControl.Instance.selectedWidth)));
+ StrCast(this.layoutDoc.strokeWidth, InkingControl.Instance.selectedWidth));
const width = right - left;
const height = bottom - top;
const scaleX = this.props.PanelWidth() / width;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b21eb9c8f..17c001971 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,6 +1,6 @@
import { MainView } from "./MainView";
import { Docs } from "../documents/Documents";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { DocServer } from "../DocServer";
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 81d427f64..dca2a1e3e 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -22,6 +22,15 @@
z-index: 1;
}
+.mainView-snapLines {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events:none;
+}
+
.mainView-container, .mainView-container-dark {
input {
color: unset !important;
@@ -39,7 +48,12 @@
}
}
+.mainView-container {
+ color:dimgray;
+}
+
.mainView-container-dark {
+ color: lightgray;
.lm_goldenlayout {
background: dimgray;
}
@@ -54,7 +68,6 @@
}
.contextMenu-cont, .contextMenu-item {
background: dimGray;
- color: lightgray;
}
.contextMenu-item:hover {
background: gray;
@@ -94,7 +107,9 @@
position: absolute;
left: 0;
bottom: 0;
- font-size: 8px;
+ border-radius: 25%;
+ margin-left: -5px;
+ background: darkblue;
}
.mainView-settings:hover {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index be46e0107..978cf7868 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,18 +1,25 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTerminal, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
+import {
+ faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo
+} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import Measure from 'react-measure';
-import { Doc, DocListCast, Field, Opt } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/FieldSymbols';
-import { List } from '../../new_fields/List';
-import { listSpec } from '../../new_fields/Schema';
-import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
-import { TraceMobx } from '../../new_fields/util';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
+import { TraceMobx } from '../../fields/util';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
@@ -41,8 +48,10 @@ import { RadialMenu } from './nodes/RadialMenu';
import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
-import { ScriptField } from '../../new_fields/ScriptField';
+import { ScriptField } from '../../fields/ScriptField';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
+import { DragManager } from '../util/DragManager';
+import { SnappingManager } from '../util/SnappingManager';
@observer
export class MainView extends React.Component {
@@ -74,12 +83,14 @@ export class MainView extends React.Component {
firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
window.removeEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("keydown", KeyManager.Instance.handle);
+ window.addEventListener("paste", KeyManager.Instance.paste as any);
}
componentWillUnMount() {
window.removeEventListener("keydown", KeyManager.Instance.handle);
window.removeEventListener("pointerdown", this.globalPointerDown);
window.removeEventListener("pointerup", this.globalPointerUp);
+ window.removeEventListener("paste", KeyManager.Instance.paste as any);
}
constructor(props: Readonly<{}>) {
@@ -103,7 +114,25 @@ export class MainView extends React.Component {
}
}
+ library.add(faTrashAlt);
+ library.add(faAngleRight);
+ library.add(faBell);
+ library.add(faTrash);
+ library.add(faCamera);
+ library.add(faExpand);
+ library.add(faCaretDown);
+ library.add(faCaretRight);
+ library.add(faCaretSquareDown);
+ library.add(faCaretSquareRight);
+ library.add(faArrowsAltH);
+ library.add(faPlus, faMinus);
library.add(faTerminal);
+ library.add(faToggleOn);
+ library.add(faLocationArrow);
+ library.add(faSearch);
+ library.add(fileSolid);
+ library.add(faFileDownload);
+ library.add(faStop);
library.add(faCalculator);
library.add(faWindowMaximize);
library.add(faFileAlt);
@@ -142,6 +171,8 @@ export class MainView extends React.Component {
library.add(faCaretUp);
library.add(faFilter);
library.add(faBullseye);
+ library.add(faArrowLeft);
+ library.add(faArrowRight);
library.add(faArrowDown);
library.add(faArrowUp);
library.add(faCloudUploadAlt);
@@ -153,6 +184,7 @@ export class MainView extends React.Component {
library.add(faPhone);
library.add(faClipboard);
library.add(faStamp);
+ library.add(faExternalLinkAlt);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -217,10 +249,10 @@ export class MainView extends React.Component {
_width: this._panelWidth * .7,
_height: this._panelHeight,
title: "Collection " + workspaceCount,
+ _LODdisable: true
};
const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myDocuments as Doc), "data", freeformDoc);
- const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myDocuments as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+ const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!]);
@@ -289,7 +321,7 @@ export class MainView extends React.Component {
defaultBackgroundColors = (doc: Doc) => {
if (this.darkScheme) {
- switch (doc.type) {
+ switch (doc?.type) {
case DocumentType.RTF || DocumentType.LABEL || DocumentType.BUTTON: return "#2d2d2d";
case DocumentType.LINK:
case DocumentType.COL: {
@@ -298,7 +330,7 @@ export class MainView extends React.Component {
default: return "black";
}
} else {
- switch (doc.type) {
+ switch (doc?.type) {
case DocumentType.RTF: return "#f1efeb";
case DocumentType.BUTTON:
case DocumentType.LABEL: return "lightgray";
@@ -461,10 +493,7 @@ export class MainView extends React.Component {
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
<button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
- Settings
- </button>
- <button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
- {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
+ <FontAwesomeIcon icon="cog" size="lg" />
</button>
</div>
{this.docButtons}
@@ -509,9 +538,9 @@ export class MainView extends React.Component {
return !this._flyoutTranslate ? (<div className="mainView-expandFlyoutButton" title="Re-attach sidebar" onPointerDown={MainView.expandFlyout}><FontAwesomeIcon icon="chevron-right" color="grey" size="lg" /></div>) : (null);
}
- addButtonDoc = (doc: Doc) => Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc);
- remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc);
- moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
+ addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+ remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+ moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
@@ -567,6 +596,15 @@ export class MainView extends React.Component {
return this._mainViewRef;
}
+ @computed get snapLines() {
+ return <div className="mainView-snapLines">
+ <svg style={{ width: "100%", height: "100%" }}>
+ {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+ {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+ </svg>
+ </div>;
+ }
+
render() {
return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
<DictationOverlay />
@@ -585,6 +623,7 @@ export class MainView extends React.Component {
<RichTextMenu />
<OverlayView />
<TimelineMenu />
+ {this.snapLines}
</div >);
}
}
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 9198fe3e3..a7bd5882d 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -4,7 +4,7 @@ import "./MainViewModal.scss";
export interface MainViewOverlayProps {
isDisplayed: boolean;
interactive: boolean;
- contents: string | JSX.Element;
+ contents: string | JSX.Element | null;
dialogueBoxStyle?: React.CSSProperties;
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
diff --git a/src/client/views/MainViewNotifs.tsx b/src/client/views/MainViewNotifs.tsx
index 82e07c449..05f890485 100644
--- a/src/client/views/MainViewNotifs.tsx
+++ b/src/client/views/MainViewNotifs.tsx
@@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { emptyFunction } from '../../Utils';
import { SetupDrag } from '../util/DragManager';
import "./MainViewNotifs.scss";
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index 5776cf070..28de0b7a5 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -9,9 +9,10 @@
}
.metadataEntry-autoSuggester {
- width: 100%;
+ width: 80%;
height: 100%;
- padding-right: 10px;
+ margin: 0;
+ display: inline-block;
}
#metadataEntry-outer {
@@ -25,7 +26,7 @@
flex-direction: column;
}
.metadataEntry-inputArea {
- display:flex;
+ display:inline-block;
flex-direction: row;
}
@@ -44,7 +45,7 @@
.react-autosuggest__input {
border: 1px solid #aaa;
border-radius: 4px;
- width: 100%;
+ width: 75%;
}
.react-autosuggest__input--focused {
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 8bc80ed06..e100d3f52 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -3,7 +3,7 @@ import "./MetadataEntryMenu.scss";
import { observer } from 'mobx-react';
import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc';
+import { Doc, Field, DocListCastAsync } from '../../fields/Doc';
import * as Autosuggest from 'react-autosuggest';
import { undoBatch } from '../util/UndoManager';
import { emptyFunction, emptyPath } from '../../Utils';
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 20aa14f84..bfa44fe47 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,9 +1,9 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc, DocListCast } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { NumCast } from "../../new_fields/Types";
+import { Doc, DocListCast, Opt } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { NumCast } from "../../fields/Types";
import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils";
import { Transform } from "../util/Transform";
import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
@@ -214,4 +214,6 @@ export class OverlayView extends React.Component {
}
}
// bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error
-Scripting.addGlobal(function addOverlayWindow(Tag: string, options: OverlayElementOptions) { const x = <ScriptingRepl />; OverlayView.Instance.addWindow(x, options); }); \ No newline at end of file
+Scripting.addGlobal(function addOverlayWindow(type: string, options: OverlayElementOptions) {
+ OverlayView.Instance.addWindow(<ScriptingRepl />, options);
+}); \ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 63744cb50..108eb83d6 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -1,8 +1,8 @@
import { IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc } from "../../new_fields/Doc";
-import { NumCast } from "../../new_fields/Types";
+import { Doc } from "../../fields/Doc";
+import { NumCast } from "../../fields/Types";
import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils";
import { Transform } from "../util/Transform";
import { DocumentView } from "./nodes/DocumentView";
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index df30c1215..dd65681d4 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -4,15 +4,18 @@ import "normalize.css";
import * as React from 'react';
import "./PreviewCursor.scss";
import { Docs } from '../documents/Documents';
-import { Doc } from '../../new_fields/Doc';
+import { Doc } from '../../fields/Doc';
import { Transform } from "../util/Transform";
+import { DocServer } from '../DocServer';
+import { undoBatch } from '../util/UndoManager';
+import { NumCast } from '../../fields/Types';
@observer
export class PreviewCursor extends React.Component<{}> {
static _onKeyPress?: (e: KeyboardEvent) => void;
static _getTransform: () => Transform;
+ static _addDocument: (doc: Doc | Doc[]) => void;
static _addLiveTextDoc: (doc: Doc) => void;
- static _addDocument: (doc: Doc) => boolean;
static _nudge: (x: number, y: number) => boolean;
@observable static _clickPoint = [0, 0];
@observable public static Visible = false;
@@ -27,50 +30,77 @@ export class PreviewCursor extends React.Component<{}> {
const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
runInAction(() => PreviewCursor.Visible = false);
+ // tests for URL and makes web document
+ const re: any = /^https?:\/\//g;
if (e.clipboardData.getData("text/plain") !== "") {
// tests for youtube and makes video document
if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
- return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
+ undoBatch(() => PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
title: url, _width: 400, _height: 315,
_nativeWidth: 600, _nativeHeight: 472.5,
x: newPoint[0], y: newPoint[1]
- }));
+ })))();
}
- // tests for URL and makes web document
- const re: any = /^https?:\/\//g;
- if (re.test(e.clipboardData.getData("text/plain"))) {
+ else if (re.test(e.clipboardData.getData("text/plain"))) {
const url = e.clipboardData.getData("text/plain");
- return PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
- title: url, _width: 500, _height: 300,
+ undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
+ title: url, _width: 500, _height: 300, UseCors: true,
// nativeWidth: 300, nativeHeight: 472.5,
x: newPoint[0], y: newPoint[1]
- }));
+ })))();
}
- // creates text document
- return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
- _width: 500,
- limitHeight: 400,
- _autoHeight: true,
- x: newPoint[0],
- y: newPoint[1],
- title: "-pasted text-"
- }));
- }
- //pasting in images
- if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
- const re: any = /<img src="(.*?)"/g;
- const arr: any[] = re.exec(e.clipboardData.getData("text/html"));
+ else if (e.clipboardData.getData("text/plain").startsWith("__DashDocId(")) {
+ const docids = e.clipboardData.getData("text/plain").split(":");
+ const strs = docids[0].split(",");
+ const ptx = Number(strs[0].substring("__DashDocId(".length));
+ const pty = Number(strs[1].substring(0, strs[1].length - 1));
+ let count = 1;
+ const list: Doc[] = [];
- return PreviewCursor._addDocument(Docs.Create.ImageDocument(
- arr[1], {
- _width: 300, title: arr[1],
- x: newPoint[0],
- y: newPoint[1],
- }));
- }
+ let first: Doc | undefined;
+ docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => {
+ count++;
+ if (doc instanceof Doc) {
+ i === 1 && (first = doc);
+ const alias = Doc.MakeClone(doc);
+ const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx;
+ const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty;
+ alias.x = newPoint[0] + deltaX;
+ alias.y = newPoint[1] + deltaY;
+ list.push(alias);
+ }
+ if (count === docids.length) {
+ undoBatch(() => PreviewCursor._addDocument(list))();
+ }
+ }));
+ e.stopPropagation();
+ } else {
+ // creates text document
+ undoBatch(() => PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
+ _width: 500,
+ limitHeight: 400,
+ _autoHeight: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ title: "-pasted text-"
+ })))();
+ }
+ } else
+ //pasting in images
+ if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
+ const re: any = /<img src="(.*?)"/g;
+ const arr: any[] = re.exec(e.clipboardData.getData("text/html"));
+
+ undoBatch(() => PreviewCursor._addDocument(Docs.Create.ImageDocument(
+ arr[1], {
+ _width: 300, title: arr[1],
+ x: newPoint[0],
+ y: newPoint[1],
+ })))();
+ }
}
}
@@ -81,7 +111,7 @@ export class PreviewCursor extends React.Component<{}> {
if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" &&
e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" &&
e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" &&
- e.key !== "NumLock" &&
+ e.key !== "NumLock" && e.key !== " " &&
(e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
!e.key.startsWith("Arrow") &&
!e.defaultPrevented) {
@@ -112,7 +142,7 @@ export class PreviewCursor extends React.Component<{}> {
onKeyPress: (e: KeyboardEvent) => void,
addLiveText: (doc: Doc) => void,
getTransform: () => Transform,
- addDocument: (doc: Doc) => boolean,
+ addDocument: (doc: Doc | Doc[]) => boolean,
nudge: (nudgeX: number, nudgeY: number) => boolean) {
this._clickPoint = [x, y];
this._onKeyPress = onKeyPress;
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index e66fd3eb4..8ca81c070 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -3,17 +3,17 @@ import React = require("react");
import { observable, action, computed, runInAction } from "mobx";
import Measure from "react-measure";
import "./RecommendationsBox.scss";
-import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { Doc, DocListCast, WidthSym, HeightSym } from "../../fields/Doc";
import { DocumentIcon } from "./nodes/DocumentIcon";
-import { StrCast, NumCast } from "../../new_fields/Types";
+import { StrCast, NumCast } from "../../fields/Types";
import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils";
import { Transform } from "../util/Transform";
-import { ObjectField } from "../../new_fields/ObjectField";
+import { ObjectField } from "../../fields/ObjectField";
import { DocumentView } from "./nodes/DocumentView";
import { DocumentType } from '../documents/DocumentTypes';
import { ClientRecommender } from "../ClientRecommender";
import { DocServer } from "../DocServer";
-import { Id } from "../../new_fields/FieldSymbols";
+import { Id } from "../../fields/FieldSymbols";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import { DocumentManager } from "../util/DocumentManager";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 153b81876..888f84dfa 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -5,11 +5,11 @@ import { observable, action } from "mobx";
import "./ScriptBox.scss";
import { OverlayView } from "./OverlayView";
import { DocumentIconContainer } from "./nodes/DocumentIcon";
-import { Opt, Doc } from "../../new_fields/Doc";
+import { Opt, Doc } from "../../fields/Doc";
import { emptyFunction } from "../../Utils";
-import { ScriptCast } from "../../new_fields/Types";
+import { ScriptCast } from "../../fields/Types";
import { CompileScript } from "../util/Scripting";
-import { ScriptField } from "../../new_fields/ScriptField";
+import { ScriptField } from "../../fields/ScriptField";
import { DragManager } from "../util/DragManager";
import { EditableView } from "./EditableView";
import { getEffectiveTypeRoots } from "typescript";
@@ -81,9 +81,9 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
);
}
//let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
- public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }) {
+ public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }, defaultScript?: ScriptField) {
let overlayDisposer: () => void = emptyFunction;
- const script = ScriptCast(doc[fieldKey]);
+ const script = ScriptCast(doc[fieldKey]) || defaultScript;
let originalText: string | undefined = undefined;
if (script) {
originalText = script.script.originalScript;
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
index 799fa9d85..e038d8213 100644
--- a/src/client/views/SearchDocBox.tsx
+++ b/src/client/views/SearchDocBox.tsx
@@ -3,10 +3,10 @@ import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
//import "./SearchBoxDoc.scss";
-import { Doc, DocListCast } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { returnFalse } from "../../Utils";
+import { Doc, DocListCast } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
+import { returnFalse, returnZero } from "../../Utils";
import { Docs } from "../documents/Documents";
import { SearchUtil } from "../util/SearchUtil";
import { EditableView } from "./EditableView";
@@ -399,7 +399,13 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
<ContentFittingDocumentView {...this.props}
Document={this.content}
rootSelected={returnFalse}
- getTransform={this.props.ScreenToLocalTransform}>
+ bringToFront={returnFalse}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ parentActive={this.props.active}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}>
</ContentFittingDocumentView>
<div
style={{
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 665ab4e41..f5e95e4fd 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -6,15 +6,15 @@ import './TemplateMenu.scss';
import { DocumentView } from "./nodes/DocumentView";
import { Template } from "./Templates";
import React = require("react");
-import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Doc, DocListCast } from "../../fields/Doc";
import { Docs, } from "../documents/Documents";
-import { StrCast, Cast } from "../../new_fields/Types";
+import { StrCast, Cast } from "../../fields/Types";
import { CollectionTreeView } from "./collections/CollectionTreeView";
import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils";
import { Transform } from "../util/Transform";
-import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
+import { ScriptField, ComputedField } from "../../fields/ScriptField";
import { Scripting } from "../util/Scripting";
-import { List } from "../../new_fields/List";
+import { List } from "../../fields/List";
@observer
class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
@@ -113,8 +113,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
render() {
const firstDoc = this.props.docViews[0].props.Document;
const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
- const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null));
- const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
+ const addedTypes = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
const layout = Doc.Layout(firstDoc);
const templateMenu: Array<JSX.Element> = [];
this.props.templates.forEach((checked, template) =>
@@ -158,9 +158,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
annotationsKey={""}
dontRegisterView={true}
fieldKey={"data"}
- moveDocument={(doc: Doc) => false}
- removeDocument={(doc: Doc) => false}
- addDocument={(doc: Doc) => false} />
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ addDocument={returnFalse} />
</ul>;
}
}
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index bbd7b2676..92b0f05b3 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -4,10 +4,10 @@ import "./Timeline.scss";
import "../globalCssVariables.scss";
import { observer } from "mobx-react";
import { observable, reaction, action, IReactionDisposer, observe, computed, runInAction, trace } from "mobx";
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { Cast, NumCast } from "../../../new_fields/Types";
-import { List } from "../../../new_fields/List";
-import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
+import { Cast, NumCast } from "../../../fields/Types";
+import { List } from "../../../fields/List";
+import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../fields/Schema";
import { Transform } from "../../util/Transform";
import { TimelineMenu } from "./TimelineMenu";
import { Docs } from "../../documents/Documents";
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 677267ca0..30692944d 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -1,12 +1,12 @@
import * as React from "react";
import "./Timeline.scss";
-import { listSpec } from "../../../new_fields/Schema";
+import { listSpec } from "../../../fields/Schema";
import { observer } from "mobx-react";
import { Track } from "./Track";
import { observable, action, computed, runInAction, IReactionDisposer, reaction, trace } from "mobx";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { List } from "../../../new_fields/List";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types";
+import { List } from "../../../fields/List";
+import { Doc, DocListCast } from "../../../fields/Doc";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlayCircle, faBackward, faForward, faGripLines, faPauseCircle, faEyeSlash, faEye, faCheckCircle, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
import { ContextMenu } from "../ContextMenu";
@@ -71,7 +71,6 @@ export class Timeline extends React.Component<FieldViewProps> {
@observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
@observable private _time = 100000; //DEFAULT
@observable private _playButton = faPlayCircle;
- @observable private _timelineVisible = false;
@observable private _mouseToggled = false;
@observable private _doubleClickEnabled = false;
@observable private _titleHeight = 0;
@@ -119,9 +118,7 @@ export class Timeline extends React.Component<FieldViewProps> {
}
componentWillUnmount() {
- runInAction(() => {
- this.props.Document.AnimationLength = this._time; //save animation length
- });
+ this.props.Document.AnimationLength = this._time; //save animation length
}
/////////////////////////////////////////////////
@@ -339,20 +336,6 @@ export class Timeline extends React.Component<FieldViewProps> {
/**
- * context menu function.
- * opens the timeline or closes the timeline.
- * Used in: Freeform
- */
- timelineContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({
- description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
- this._timelineVisible = !this._timelineVisible;
- }), icon: this._timelineVisible ? faEyeSlash : faEye
- });
- }
-
-
- /**
* timeline zoom function
* use mouse middle button to zoom in/out the timeline
*/
@@ -465,7 +448,7 @@ export class Timeline extends React.Component<FieldViewProps> {
<div key="round-toggle-slider" ref={this._roundToggleRef} className="round-toggle-slider" onPointerDown={this.toggleChecked}> </div>
</div>
</div>
- <div className="time-box overview-tool" style={{ display: this._timelineVisible ? "flex" : "none" }}>
+ <div className="time-box overview-tool" style={{ display: "flex" }}>
{this.timeIndicator(lengthString, totalTime)}
<div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}><FontAwesomeIcon icon="compress-arrows-alt" size="lg" /></div>
<div className="resetView-tool" style={{ display: this._isAuthoring ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div>
@@ -483,10 +466,16 @@ export class Timeline extends React.Component<FieldViewProps> {
);
}
else {
+ const ctime = `Current: ${this.getCurrentTime()}`;
+ const ttime = `Total: ${this.toReadTime(this._time)}`;
return (
<div style={{ flexDirection: "column" }}>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>{`Current: ${this.getCurrentTime()}`}</div>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>{`Total: ${this.toReadTime(this._time)}`}</div>
+ <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ {ctime}
+ </div>
+ <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ {ttime}
+ </div>
</div>
);
}
@@ -536,8 +525,8 @@ export class Timeline extends React.Component<FieldViewProps> {
@action.bound
changeLengths() {
if (this._infoContainer.current) {
- this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
- this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts
+ this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
+ this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts
}
}
@@ -603,8 +592,8 @@ export class Timeline extends React.Component<FieldViewProps> {
trace();
// change visible and total width
return (
- <div style={{ visibility: this._timelineVisible ? "visible" : "hidden" }}>
- <div key="timeline_wrapper" style={{ visibility: BoolCast(this.props.Document.isATOn && this._timelineVisible) ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
+ <div style={{ visibility: "visible" }}>
+ <div key="timeline_wrapper" style={{ visibility: BoolCast(this.props.Document.isATOn) ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
<div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
<div key="timeline_info" className="info-container" ref={this._infoContainer} onWheel={this.onWheelZoom}>
{this.drawTicks()}
@@ -613,7 +602,7 @@ export class Timeline extends React.Component<FieldViewProps> {
</div>
<div key="timeline_trackbox" className="trackbox" ref={this._trackbox} onPointerDown={this.onPanDown} style={{ width: `${this._totalLength}px` }}>
{DocListCast(this.children).map(doc =>
- <Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={this._timelineVisible} />
+ <Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} />
)}
</div>
</div>
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 59c25596e..53ca9acad 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
-import {observable, action, runInAction} from "mobx";
-import {observer} from "mobx-react";
-import "./TimelineMenu.scss";
+import { observable, action, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import "./TimelineMenu.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChartLine, faRoad, faClipboard, faPen, faTrash, faTable } from "@fortawesome/free-solid-svg-icons";
import { Utils } from "../../../Utils";
@@ -9,67 +9,66 @@ import { Utils } from "../../../Utils";
@observer
export class TimelineMenu extends React.Component {
- public static Instance:TimelineMenu;
+ public static Instance: TimelineMenu;
@observable private _opacity = 0;
- @observable private _x = 0;
- @observable private _y = 0;
- @observable private _currentMenu:JSX.Element[] = [];
+ @observable private _x = 0;
+ @observable private _y = 0;
+ @observable private _currentMenu: JSX.Element[] = [];
- constructor (props:Readonly<{}>){
- super(props);
- TimelineMenu.Instance = this;
+ constructor(props: Readonly<{}>) {
+ super(props);
+ TimelineMenu.Instance = this;
}
-
+
@action
- openMenu = (x?:number, y?:number) => {
- this._opacity = 1;
- x ? this._x = x : this._x = 0;
- y ? this._y = y : this._y = 0;
+ openMenu = (x?: number, y?: number) => {
+ this._opacity = 1;
+ x ? this._x = x : this._x = 0;
+ y ? this._y = y : this._y = 0;
}
@action
closeMenu = () => {
- this._opacity = 0;
- this._currentMenu = [];
- this._x = -1000000;
- this._y = -1000000;
+ this._opacity = 0;
+ this._currentMenu = [];
+ this._x = -1000000;
+ this._y = -1000000;
}
@action
- addItem = (type: "input" | "button", title: string, event: (e:any, ...args:any[]) => void) => {
- if (type === "input"){
- let inputRef = React.createRef<HTMLInputElement>();
- let text = "";
- this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faClipboard} size="lg"/><input className="timeline-menu-input" ref = {inputRef} placeholder={title} onChange={(e) => {
+ addItem = (type: "input" | "button", title: string, event: (e: any, ...args: any[]) => void) => {
+ if (type === "input") {
+ const inputRef = React.createRef<HTMLInputElement>();
+ let text = "";
+ this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faClipboard} size="lg" /><input className="timeline-menu-input" ref={inputRef} placeholder={title} onChange={(e) => {
e.stopPropagation();
text = e.target.value;
}} onKeyDown={(e) => {
if (e.keyCode === 13) {
- event(text);
- this.closeMenu();
- e.stopPropagation();
- }
- }}/></div>);
+ event(text);
+ this.closeMenu();
+ e.stopPropagation();
+ }
+ }} /></div>);
} else if (type === "button") {
- let buttonRef = React.createRef<HTMLDivElement>();
- this._currentMenu.push( <div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faChartLine}size="lg"/><p className="timeline-menu-desc" onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- event(e);
- this.closeMenu();
- }}>{title}</p></div>);
- }
+ this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faChartLine} size="lg" /><p className="timeline-menu-desc" onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ event(e);
+ this.closeMenu();
+ }}>{title}</p></div>);
+ }
}
- @action
- addMenu = (title:string) => {
- this._currentMenu.unshift(<div key={Utils.GenerateGuid()} className="timeline-menu-header"><p className="timeline-menu-header-desc">{title}</p></div>);
+ @action
+ addMenu = (title: string) => {
+ this._currentMenu.unshift(<div key={Utils.GenerateGuid()} className="timeline-menu-header"><p className="timeline-menu-header-desc">{title}</p></div>);
}
render() {
return (
- <div key={Utils.GenerateGuid()} className="timeline-menu-container" style={{opacity: this._opacity, left: this._x, top: this._y}} >
+ <div key={Utils.GenerateGuid()} className="timeline-menu-container" style={{ opacity: this._opacity, left: this._x, top: this._y }} >
{this._currentMenu}
</div>
);
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 79eb60fae..fc96c320a 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -1,12 +1,12 @@
import { action, computed, intercept, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../new_fields/Doc";
-import { Copy } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { listSpec } from "../../../new_fields/Schema";
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../fields/Doc";
+import { Copy } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { ObjectField } from "../../../fields/ObjectField";
+import { listSpec } from "../../../fields/Schema";
+import { Cast, NumCast } from "../../../fields/Types";
import { Transform } from "../../util/Transform";
import { Keyframe, KeyframeFunc, RegionData } from "./Keyframe";
import "./Track.scss";
@@ -90,9 +90,9 @@ export class Track extends React.Component<IProps> {
*/
@action
saveKeyframe = async () => {
- let keyframes = Cast(this.saveStateRegion?.keyframes, listSpec(Doc)) as List<Doc>;
- let kfIndex = keyframes.indexOf(this.saveStateKf!);
- let kf = keyframes[kfIndex] as Doc; //index in the keyframe
+ const keyframes = Cast(this.saveStateRegion?.keyframes, listSpec(Doc)) as List<Doc>;
+ const kfIndex = keyframes.indexOf(this.saveStateKf!);
+ const kf = keyframes[kfIndex] as Doc; //index in the keyframe
if (this._newKeyframe) {
DocListCast(this.saveStateRegion?.keyframes).forEach((kf, index) => {
this.copyDocDataToKeyFrame(kf);
@@ -103,17 +103,17 @@ export class Track extends React.Component<IProps> {
if (!kf) return;
if (kf.type === KeyframeFunc.KeyframeType.default) { // only save for non-fades
this.copyDocDataToKeyFrame(kf);
- let leftkf = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, kf); // lef keyframe, if it exists
- let rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists
+ const leftkf = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, kf); // lef keyframe, if it exists
+ const rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists
if (leftkf?.type === KeyframeFunc.KeyframeType.fade) { //replicating this keyframe to fades
- let edge = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, leftkf);
+ const edge = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, leftkf);
edge && this.copyDocDataToKeyFrame(edge);
leftkf && this.copyDocDataToKeyFrame(leftkf);
- edge && (edge!.opacity = 0.1);
- leftkf && (leftkf!.opacity = 1);
+ edge && (edge.opacity = 0.1);
+ leftkf && (leftkf.opacity = 1);
}
if (rightkf?.type === KeyframeFunc.KeyframeType.fade) {
- let edge = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, rightkf);
+ const edge = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, rightkf);
edge && this.copyDocDataToKeyFrame(edge);
rightkf && this.copyDocDataToKeyFrame(rightkf);
edge && (edge.opacity = 0.1);
@@ -142,7 +142,7 @@ export class Track extends React.Component<IProps> {
//check for region
const region = this.findRegion(this.time);
if (region !== undefined) { //if region at scrub time exist
- let r = region as RegionData; //for some region is returning undefined... which is not the case
+ const r = region as RegionData; //for some region is returning undefined... which is not the case
if (DocListCast(r.keyframes).find(kf => kf.time === this.time) === undefined) { //basically when there is no additional keyframe at that timespot
this.makeKeyData(r, this.time, KeyframeFunc.KeyframeType.default);
}
@@ -222,11 +222,11 @@ export class Track extends React.Component<IProps> {
} else if (this._newKeyframe) {
await this.saveKeyframe();
}
- let regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
+ const regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
if (regiondata) {
- let leftkf: (Doc | undefined) = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
- let rightkf: (Doc | undefined) = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists
- let currentkf: (Doc | undefined) = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
+ const leftkf: (Doc | undefined) = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
+ const rightkf: (Doc | undefined) = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists
+ const currentkf: (Doc | undefined) = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
if (currentkf) {
console.log("is current");
await this.applyKeys(currentkf);
@@ -248,7 +248,7 @@ export class Track extends React.Component<IProps> {
if (!kf[key]) {
this.props.node[key] = undefined;
} else {
- let stored = kf[key];
+ const stored = kf[key];
this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
@@ -261,7 +261,7 @@ export class Track extends React.Component<IProps> {
@action
calcCurrent = (region: Doc) => {
let currentkf: (Doc | undefined) = undefined;
- let keyframes = DocListCast(region.keyframes!);
+ const keyframes = DocListCast(region.keyframes!);
keyframes.forEach((kf) => {
if (NumCast(kf.time) === Math.round(this.time)) currentkf = kf;
});
@@ -276,12 +276,12 @@ export class Track extends React.Component<IProps> {
interpolate = async (left: Doc, right: Doc) => {
this.primitiveWhitelist.forEach(key => {
if (left[key] && right[key] && typeof (left[key]) === "number" && typeof (right[key]) === "number") { //if it is number, interpolate
- let dif = NumCast(right[key]) - NumCast(left[key]);
- let deltaLeft = this.time - NumCast(left.time);
- let ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
+ const dif = NumCast(right[key]) - NumCast(left[key]);
+ const deltaLeft = this.time - NumCast(left.time);
+ const ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
this.props.node[key] = NumCast(left[key]) + (dif * ratio);
} else { // case data
- let stored = left[key];
+ const stored = left[key];
this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
@@ -301,8 +301,8 @@ export class Track extends React.Component<IProps> {
*/
@action
onInnerDoubleClick = (e: React.MouseEvent) => {
- let inner = this._inner.current!;
- let offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
+ const inner = this._inner.current!;
+ const offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
this.createRegion(KeyframeFunc.convertPixelTime(offsetX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement));
}
@@ -313,10 +313,10 @@ export class Track extends React.Component<IProps> {
@action
createRegion = (time: number) => {
if (this.findRegion(time) === undefined) { //check if there is a region where double clicking (prevents phantom regions)
- let regiondata = KeyframeFunc.defaultKeyframe(); //create keyframe data
+ const regiondata = KeyframeFunc.defaultKeyframe(); //create keyframe data
regiondata.position = time; //set position
- let rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions);
+ const rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions);
if (rightRegion && rightRegion.position - regiondata.position <= 4000) { //edge case when there is less than default 4000 duration space between this and right region
regiondata.duration = rightRegion.position - regiondata.position;
@@ -332,7 +332,7 @@ export class Track extends React.Component<IProps> {
@action
makeKeyData = (regiondata: RegionData, time: number, type: KeyframeFunc.KeyframeType = KeyframeFunc.KeyframeType.default) => { //Kfpos is mouse offsetX, representing time
- const trackKeyFrames = DocListCast(regiondata.keyframes)!;
+ const trackKeyFrames = DocListCast(regiondata.keyframes);
const existingkf = trackKeyFrames.find(TK => TK.time === time);
if (existingkf) return existingkf;
//else creates a new doc.
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index eda8e5684..39bb9bc23 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -2,21 +2,22 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observable, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { makeInterface } from '../../../new_fields/Schema';
-import { NumCast, StrCast } from '../../../new_fields/Types';
+import { documentSchema, collectionSchema } from '../../../fields/documentSchemas';
+import { makeInterface } from '../../../fields/Schema';
+import { NumCast, StrCast, ScriptCast, Cast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import "./CollectionCarouselView.scss";
import { CollectionSubView } from './CollectionSubView';
import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
-import { Doc } from '../../../new_fields/Doc';
+import { Doc } from '../../../fields/Doc';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { ContextMenu } from '../ContextMenu';
-import { ObjectField } from '../../../new_fields/ObjectField';
+import { ObjectField } from '../../../fields/ObjectField';
+import { returnFalse } from '../../../Utils';
-type CarouselDocument = makeInterface<[typeof documentSchema,]>;
-const CarouselDocument = makeInterface(documentSchema);
+type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const CarouselDocument = makeInterface(documentSchema, collectionSchema);
@observer
export class CollectionCarouselView extends CollectionSubView(CarouselDocument) {
@@ -39,7 +40,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
e.stopPropagation();
this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
}
-
panelHeight = () => this.props.PanelHeight() - 50;
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
@@ -47,11 +47,18 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
<>
<div className="collectionCarouselView-image" key="image">
<ContentFittingDocumentView {...this.props}
+ onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
+ onClick={ScriptCast(this.layoutDoc.onChildClick)}
renderDepth={this.props.renderDepth + 1}
+ LayoutTemplate={this.props.ChildLayoutTemplate}
+ LayoutTemplateString={this.props.ChildLayoutString}
Document={this.childLayoutPairs[index].layout}
- DataDocument={this.childLayoutPairs[index].data}
+ DataDoc={this.childLayoutPairs[index].data}
PanelHeight={this.panelHeight}
- getTransform={this.props.ScreenToLocalTransform} />
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ bringToFront={returnFalse}
+ parentActive={this.props.active}
+ />
</div>
<div className="collectionCarouselView-caption" key="caption"
style={{
@@ -60,9 +67,9 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
borderRadius: StrCast(this.layoutDoc._captionBorderRounding),
}}>
<FormattedTextBox key={index} {...this.props}
- xMargin={NumCast(this.layoutDoc["caption-xMargin"])}
- yMargin={NumCast(this.layoutDoc["caption-yMargin"])}
- Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+ xMargin={NumCast(this.layoutDoc["_carousel-caption-xMargin"])}
+ yMargin={NumCast(this.layoutDoc["_carousel-caption-yMargin"])}
+ Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"} />
</div>
</>;
}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0d859c3f1..745476ef7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -7,13 +7,13 @@ import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import Measure from "react-measure";
import * as GoldenLayout from "../../../client/goldenLayout";
-import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast, Field, Opt, DataSym } from "../../../new_fields/Doc";
-import { Id } from '../../../new_fields/FieldSymbols';
-import { List } from '../../../new_fields/List';
-import { FieldId } from "../../../new_fields/RefField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { TraceMobx } from '../../../new_fields/util';
+import { DateField } from '../../../fields/DateField';
+import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc";
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { FieldId } from "../../../fields/RefField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
@@ -30,6 +30,7 @@ import { SubCollectionViewProps } from "./CollectionSubView";
import { DockingViewButtonSelector } from './ParentDocumentSelector';
import React = require("react");
import { CollectionViewType } from './CollectionView';
+import { SnappingManager } from '../../util/SnappingManager';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -68,10 +69,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
//Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
+ DragManager.StartWindowDrag = this.StartOtherDrag;
}
- hack: boolean = false;
- undohack: any = null;
- public StartOtherDrag(e: any, dragDocs: Doc[]) {
+ public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
let config: any;
if (dragDocs.length === 1) {
config = CollectionDockingView.makeDocumentConfig(dragDocs[0]);
@@ -190,6 +190,30 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
return retVal;
}
+ @undoBatch
+ @action
+ public static ReplaceTab(document: Doc, stack: any): Opt<Doc> {
+ if (!CollectionDockingView.Instance) return undefined;
+ const instance = CollectionDockingView.Instance;
+ const replaceTab = (doc: Doc, child: any): Opt<Doc> => {
+ for (const contentItem of child.contentItems) {
+ const { config, isStack, isRow, isColumn } = contentItem;
+ if (isRow || isColumn || isStack) {
+ const val = replaceTab(doc, contentItem);
+ if (val) return val;
+ } else if (config.component === "DocumentFrameRenderer" &&
+ config.props.documentId === doc[Id]) {
+ const alias = Doc.MakeAlias(doc);
+ config.props.documentId = alias[Id];
+ config.title = alias.title;
+ instance.stateChanged();
+ return alias;
+ }
+ }
+ return undefined;
+ };
+ return replaceTab(document, instance._goldenLayout.root);
+ }
//
// Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
@@ -453,12 +477,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const json = JSON.stringify(this._goldenLayout.toConfig());
this.props.Document.dockingConfig = json;
this.updateDataField(json);
-
- if (this.undohack && !this.hack) {
- this.undohack.end();
- this.undohack = undefined;
- }
- this.hack = false;
}
itemDropped = () => {
@@ -499,7 +517,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const stack = tab.contentItem.parent;
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: any) => {
- if (!this._isPointerDown || !SelectionManager.GetIsDragging()) return;
+ if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return;
const activeContentItem = tab.header.parent.getActiveContentItem();
if (tab.contentItem !== activeContentItem) {
tab.header.parent.setActiveContentItem(tab.contentItem);
@@ -515,15 +533,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
}
};
- let rendered = false;
+
tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
(views) => {
- !rendered && ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
- <DockingViewButtonSelector views={views} Stack={stack} />
- </span>,
- gearSpan);
- rendered = true;
- });
+ if (views.length) {
+ ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
+ <DockingViewButtonSelector views={() => views} Stack={stack} />
+ </span>,
+ gearSpan);
+ tab.buttonDisposer?.();
+ }
+ }, { fireImmediately: true });
tab.reactComponents = [gearSpan];
tab.element.append(gearSpan);
@@ -679,15 +699,20 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
**/
@undoBatch
@action
- public static PinDoc(doc: Doc) {
- //add this new doc to props.Document
- const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
- if (curPres) {
- const pinDoc = Doc.MakeAlias(doc);
- pinDoc.presentationTargetDoc = doc;
- Doc.AddDocToList(curPres, "data", pinDoc);
- if (!DocumentManager.Instance.getDocumentView(curPres)) {
- CollectionDockingView.AddRightSplit(curPres);
+ public static PinDoc(doc: Doc, unpin = false) {
+ if (unpin) DockedFrameRenderer.UnpinDoc(doc);
+ else {
+ //add this new doc to props.Document
+ const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+ if (curPres) {
+ const pinDoc = Doc.MakeAlias(doc);
+ pinDoc.presentationTargetDoc = doc;
+ pinDoc.presZoomButton = true;
+ pinDoc.context = curPres;
+ Doc.AddDocToList(curPres, "data", pinDoc);
+ if (!DocumentManager.Instance.getDocumentView(curPres)) {
+ CollectionDockingView.AddRightSplit(curPres);
+ }
}
}
}
@@ -732,8 +757,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
get layoutDoc() { return this._document && Doc.Layout(this._document); }
- panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : this._panelWidth;
- panelHeight = () => this._panelHeight;
+ nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0;
+ panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
+ (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
+ panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
@@ -771,7 +798,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
return Transform.Identity();
}
get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
- get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this.panelWidth() * 100}%` : undefined; }
+ get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}%` : undefined; }
addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
SelectionManager.DeselectAll();
@@ -781,6 +808,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
return CollectionDockingView.AddRightSplit(doc, libraryPath);
} else if (location === "close") {
return CollectionDockingView.CloseRightSplit(doc);
+ } else if (location === "replace") {
+ const alias = CollectionDockingView.ReplaceTab(doc, this._stack);
+ if (alias) {
+ runInAction(() => this._document = alias);
+ return true;
+ }
+ return false;
} else {// if (location === "inPlace") {
return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
}
@@ -802,8 +836,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
ContentScaling={this.contentScaling}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
+ NativeHeight={this.nativeHeight}
+ NativeWidth={this.nativeWidth}
ScreenToLocalTransform={this.ScreenToLocalTransform}
renderDepth={0}
parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 344dca23a..f1002044a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -1,9 +1,9 @@
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
-import { makeInterface } from '../../../new_fields/Schema';
-import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../new_fields/Types';
+import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { makeInterface } from '../../../fields/Schema';
+import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../fields/Types';
import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { Transform } from '../../util/Transform';
@@ -11,8 +11,8 @@ import "./CollectionLinearView.scss";
import { CollectionViewType } from './CollectionView';
import { CollectionSubView } from './CollectionSubView';
import { DocumentView } from '../nodes/DocumentView';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id } from '../../../new_fields/FieldSymbols';
+import { documentSchema } from '../../../fields/documentSchemas';
+import { Id } from '../../../fields/FieldSymbols';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index 971224482..d91337ce9 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -1,10 +1,10 @@
import { GoogleApiWrapper, Map as GeoMap, IMapProps, Marker } from "google-maps-react";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
+import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../fields/Doc";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
+import { makeInterface } from "../../../fields/Schema";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
import "./CollectionMapView.scss";
import { CollectionSubView } from "./CollectionSubView";
import React = require("react");
@@ -47,7 +47,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
private _cancelAddrReq = new Map<string, boolean>();
private _cancelLocReq = new Map<string, boolean>();
private _initialLookupPending = new Map<string, boolean>();
- private responders: { location: Lambda, address: Lambda }[] = [];
+ private responders: { locationDisposer: Lambda, addressDisposer: Lambda }[] = [];
/**
* Note that all the uses of runInAction below are not included
@@ -176,13 +176,16 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
}
@computed get reactiveContents() {
- this.responders.forEach(({ location, address }) => { location(); address(); });
+ this.responders.forEach(({ locationDisposer, addressDisposer }) => {
+ locationDisposer();
+ addressDisposer();
+ });
this.responders = [];
return this.childLayoutPairs.map(({ layout }) => {
const fieldKey = Doc.LayoutFieldKey(layout);
const id = layout[Id];
this.responders.push({
- location: computed(() => ({ lat: layout[`${fieldKey}-lat`], lng: layout[`${fieldKey}-lng`] }))
+ locationDisposer: computed(() => ({ lat: layout[`${fieldKey}-lat`], lng: layout[`${fieldKey}-lng`] }))
.observe(({ oldValue, newValue }) => {
if (this._cancelLocReq.get(id)) {
this._cancelLocReq.set(id, false);
@@ -190,7 +193,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
this.respondToLocationChange(layout, fieldKey, newValue, oldValue);
}
}),
- address: computed(() => Cast(layout[`${fieldKey}-address`], "string", null))
+ addressDisposer: computed(() => Cast(layout[`${fieldKey}-address`], "string", null))
.observe(({ oldValue, newValue }) => {
if (this._cancelAddrReq.get(id)) {
this._cancelAddrReq.set(id, false);
@@ -206,7 +209,8 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
render() {
const { childLayoutPairs } = this;
const { Document, fieldKey, active, google } = this.props;
- let center = this.getLocation(Document, `${fieldKey}-mapCenter`, false);
+ const mapLoc = this.getLocation(this.rootDoc, `${fieldKey}-mapCenter`, false);
+ let center = mapLoc;
if (center === undefined) {
const childLocations = childLayoutPairs.map(({ layout }) => this.getLocation(layout, Doc.LayoutFieldKey(layout), false));
center = childLocations.find(location => location) || defaultLocation;
@@ -246,6 +250,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
}}
>
{this.reactiveContents}
+ {mapLoc ? this.renderMarker(this.rootDoc) : undefined}
</GeoMap>
</div>
</div>;
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 7ad15ef41..d6cb174cc 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -4,20 +4,20 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { StrCast, NumCast } from "../../../new_fields/Types";
+import { Doc } from "../../../fields/Doc";
+import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { ScriptField } from "../../../fields/ScriptField";
+import { StrCast, NumCast } from "../../../fields/Types";
import { numberRange, setupMoveUpEvents, emptyFunction } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
-import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
import "./CollectionStackingView.scss";
+import { SnappingManager } from "../../util/SnappingManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -132,7 +132,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
this._color = color;
}
- pointerEnteredRow = action(() => SelectionManager.GetIsDragging() && (this._background = "#b4b4b4"));
+ pointerEnteredRow = action(() => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4"));
@action
pointerLeaveRow = () => {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 3bbfcc4d7..e3bcf2a21 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,8 +1,8 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { ScriptField } from "../../../fields/ScriptField";
+import { BoolCast, NumCast, StrCast } from "../../../fields/Types";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
@@ -12,6 +12,7 @@ import React = require("react");
import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
import { SelectionManager } from "../../util/SelectionManager";
import { UndoManager } from "../../util/UndoManager";
+import { SnappingManager } from "../../util/SnappingManager";
@observer
export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -53,7 +54,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
toggleStarburst = action(() => {
if (this._layoutEngine === 'starburst') {
const defaultSize = 110;
- this.layoutDoc.overflow = undefined;
+ this.layoutDoc._overflow = undefined;
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
@@ -61,7 +62,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
this._layoutEngine = 'pass';
} else {
const defaultSize = 25;
- this.layoutDoc.overflow = 'visible';
+ this.layoutDoc._overflow = 'visible';
!this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
!this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
if (this._layoutEngine === 'pass') {
@@ -78,7 +79,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
_undoBatch: UndoManager.Batch | undefined;
pointerDown = (e: React.PointerEvent) => {
let dist = 0;
- SelectionManager.SetIsDragging(true);
+ SnappingManager.SetIsDragging(true);
// this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
@@ -98,7 +99,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}, () => {
this._undoBatch?.end();
this._undoBatch = undefined;
- SelectionManager.SetIsDragging(false);
+ SnappingManager.SetIsDragging(false);
if (!this.childDocs.length) {
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 82204ca7b..62aed67ed 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -4,8 +4,8 @@ import { observer } from "mobx-react";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
-import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
import { KeyCodes } from "../../util/KeyCodes";
import { SetupDrag, DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
@@ -16,13 +16,13 @@ import { EditableView } from "../EditableView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
import { CollectionView } from "./CollectionView";
-import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../new_fields/Types";
+import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../fields/Types";
import { Docs } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faExpand } from '@fortawesome/free-solid-svg-icons';
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
+import { SnappingManager } from "../../util/SnappingManager";
library.add(faExpand);
@@ -37,8 +37,8 @@ export interface CellProps {
renderDepth: number;
addDocTab: (document: Doc, where: string) => boolean;
pinToPres: (document: Doc) => void;
- moveDocument: (document: Doc, targetCollection: Doc | undefined,
- addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined,
+ addDocument: (document: Doc | Doc[]) => boolean) => boolean;
isFocused: boolean;
changeFocusedCellByIndex: (row: number, col: number) => void;
setIsEditing: (isEditing: boolean) => void;
@@ -185,11 +185,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const onItemDown = (e: React.PointerEvent) => {
fieldIsDoc && SetupDrag(this._focusRef,
() => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
- this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc | undefined, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
+ this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument,
this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
};
const onPointerEnter = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
+ if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) {
dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
}
};
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 507ee89e4..dae0600b1 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -7,7 +7,7 @@ import { library, IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ColumnType } from "./CollectionSchemaView";
import { faFile } from "@fortawesome/free-regular-svg-icons";
-import { SchemaHeaderField, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 972714e34..6f1e8ac1f 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -2,18 +2,18 @@ import React = require("react");
import { ReactTableDefaults, TableCellRenderer, RowInfo } from "react-table";
import "./CollectionSchemaView.scss";
import { Transform } from "../../util/Transform";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc } from "../../../fields/Doc";
import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, StrCast } from "../../../fields/Types";
import { ContextMenu } from "../ContextMenu";
import { action } from "mobx";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faGripVertical, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { DocumentManager } from "../../util/DocumentManager";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
+import { SnappingManager } from "../../util/SnappingManager";
library.add(faGripVertical, faTrash);
@@ -32,7 +32,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
onPointerEnter = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+ if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
this._header!.current!.className = "collectionSchema-col-wrapper";
document.addEventListener("pointermove", this.onDragMove, true);
}
@@ -130,8 +130,8 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
export interface MovableRowProps {
rowInfo: RowInfo;
ScreenToLocalTransform: () => Transform;
- addDoc: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
- removeDoc: (doc: Doc) => boolean;
+ addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
+ removeDoc: (doc: Doc | Doc[]) => boolean;
rowFocused: boolean;
textWrapRow: (doc: Doc) => void;
rowWrapped: boolean;
@@ -143,7 +143,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
private _rowDropDisposer?: DragManager.DragDropDisposer;
onPointerEnter = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+ if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
this._header!.current!.className = "collectionSchema-row-wrapper";
document.addEventListener("pointermove", this.onDragMove, true);
}
@@ -183,7 +183,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (docDragData) {
e.stopPropagation();
if (docDragData.draggedDocuments[0] === rowDoc) return true;
- const addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before);
+ const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
const movedDocs = docDragData.draggedDocuments;
return (docDragData.dropAction || docDragData.userDropAction) ?
docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
@@ -201,7 +201,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
@undoBatch
@action
- move: DragManager.MoveFunction = (doc: Doc, targetCollection: Doc | undefined, addDoc) => {
+ move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
if (targetView && targetView.props.ContainingCollectionDoc) {
return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 380d91d2f..35f892d65 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -6,13 +6,13 @@ import { action, computed, observable, untracked } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
import "react-table/react-table.css";
-import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { listSpec } from "../../../fields/Schema";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../fields/ScriptField";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types";
import { Docs, DocumentOptions } from "../../documents/Documents";
import { CompileScript, Transformer, ts } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
@@ -27,7 +27,7 @@ import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionView } from "./CollectionView";
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
+import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
import { DocumentView } from "../nodes/DocumentView";
library.add(faCog, faPlus, faSortUp, faSortDown);
@@ -121,7 +121,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
{!this.previewDocument ? (null) :
<ContentFittingDocumentView
Document={this.previewDocument}
- DataDocument={undefined}
+ DataDoc={undefined}
NativeHeight={returnZero}
NativeWidth={returnZero}
fitToBox={true}
@@ -132,16 +132,18 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
rootSelected={this.rootSelected}
PanelWidth={this.previewWidth}
PanelHeight={this.previewHeight}
- getTransform={this.getPreviewTransform}
- CollectionDoc={this.props.CollectionView?.props.Document}
- CollectionView={this.props.CollectionView}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
moveDocument={this.props.moveDocument}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
- active={this.props.active}
+ parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
/>}
</div>;
}
@@ -205,9 +207,9 @@ export interface SchemaTableProps {
ContainingCollectionDoc: Opt<Doc>;
fieldKey: string;
renderDepth: number;
- deleteDocument: (document: Doc) => boolean;
- addDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+ deleteDocument: (document: Doc | Doc[]) => boolean;
+ addDocument: (document: Doc | Doc[]) => boolean;
+ moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: (outsideReaction: boolean) => boolean;
onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 47faa9239..5eaf29316 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -180,13 +180,16 @@
.collectionStackingView-sectionHeader-subCont {
outline: none;
border: 0px;
- color: $light-color;
width: 100%;
- color: grey;
letter-spacing: 2px;
font-size: 75%;
transition: transform 0.2s;
position: relative;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: lightGray;
.editableView-container-editing-oneLine,
.editableView-container-editing {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e3720bf01..cc6077d98 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,14 +4,15 @@ import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
-import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
+import { DataSym, Doc, HeightSym, WidthSym } from "../../../fields/Doc";
+import { collectionSchema, documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { listSpec, makeInterface } from "../../../fields/Schema";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, smoothScroll } from "../../../Utils";
import { DragManager, dropActionType } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -24,11 +25,14 @@ import "./CollectionStackingView.scss";
import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
+import { SnappingManager } from "../../util/SnappingManager";
const _global = (window /* browser */ || global /* node */) as any;
+type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
+const StackingDocument = makeInterface(collectionSchema, documentSchema);
+
@observer
-export class CollectionStackingView extends CollectionSubView(doc => doc) {
+export class CollectionStackingView extends CollectionSubView(StackingDocument) {
_masonryGridRef: HTMLDivElement | null = null;
_draggerRef = React.createRef<HTMLDivElement>();
_pivotFieldDisposer?: IReactionDisposer;
@@ -53,6 +57,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
+ constructor(props: any) {
+ super(props);
+
+ if (this.sectionHeaders === undefined) {
+ this.props.Document.sectionHeaders = new List<SchemaHeaderField>();
+ }
+ }
+
children(docs: Doc[], columns?: number) {
TraceMobx();
this._docXfs.length = 0;
@@ -61,7 +73,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const width = () => this.getDocWidth(d);
const dref = React.createRef<HTMLDivElement>();
const dxf = () => this.getDocTransform(d, dref.current!);
- this._docXfs.push({ dxf: dxf, width: width, height: height });
+ this._docXfs.push({ dxf, width, height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
@@ -81,7 +93,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0);
return new Map<SchemaHeaderField, Doc[]>();
}
- const sectionHeaders: SchemaHeaderField[] = Array.from(this.sectionHeaders);
+ const sectionHeaders = Array.from(this.sectionHeaders);
const fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
let changed = false;
this.filteredChildren.map(d => {
@@ -116,7 +128,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
getSimpleDocHeight(d?: Doc) {
if (!d) return 0;
- const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
const nw = NumCast(layoutDoc._nativeWidth);
const nh = NumCast(layoutDoc._nativeHeight);
let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -128,7 +140,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym]();
}
componentDidMount() {
- super.componentDidMount();
+ super.componentDidMount?.();
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
@@ -150,8 +162,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
+ @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
@@ -160,14 +172,33 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
return this.props.addDocTab(doc, where);
}
+
+
+ focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
+ Doc.BrushDoc(doc);
+ this.props.focus(doc);
+ Doc.linkFollowHighlight(doc);
+
+ const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
+ if (found) {
+ const top = found.getBoundingClientRect().top;
+ const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+ smoothScroll(500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+ }
+ afterFocus && setTimeout(() => {
+ if (afterFocus?.()) { }
+ }, 500);
+ }
+
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
- const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
+ const layoutDoc = Doc.Layout(doc, this.props.ChildLayoutTemplate?.());
const height = () => this.getDocHeight(doc);
return <ContentFittingDocumentView
Document={doc}
- DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+ DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
backgroundColor={this.props.backgroundColor}
- LayoutDoc={this.props.childLayoutTemplate}
+ LayoutTemplate={this.props.ChildLayoutTemplate}
+ LayoutTemplateString={this.props.ChildLayoutString}
LibraryPath={this.props.LibraryPath}
FreezeDimensions={this.props.freezeChildDimensions}
renderDepth={this.props.renderDepth + 1}
@@ -175,33 +206,37 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
PanelHeight={height}
NativeHeight={returnZero}
NativeWidth={returnZero}
- fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ fitToBox={false}
+ dontRegisterView={this.props.dontRegisterView}
rootSelected={this.rootSelected}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
- onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
- getTransform={dxf}
- focus={this.props.focus}
- CollectionDoc={this.props.CollectionView?.props.Document}
- CollectionView={this.props.CollectionView}
+ onClick={this.onChildClickHandler}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={dxf}
+ focus={this.focusDocument}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
- active={this.props.active}
+ parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.addDocTab}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
pinToPres={this.props.pinToPres}
/>;
}
getDocWidth(d?: Doc) {
if (!d) return 0;
- const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
const nw = NumCast(layoutDoc._nativeWidth);
return Math.min(nw && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
}
getDocHeight(d?: Doc) {
if (!d) return 0;
- const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
const nw = NumCast(layoutDoc._nativeWidth);
const nh = NumCast(layoutDoc._nativeHeight);
let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -302,7 +337,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
this.refList.push(ref);
const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
this.observer = new _global.ResizeObserver(action((entries: any) => {
- if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
+ if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
}
}));
@@ -349,7 +384,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
this.refList.push(ref);
const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
this.observer = new _global.ResizeObserver(action((entries: any) => {
- if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
+ if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
}
}));
@@ -397,6 +432,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
if (!e.isPropagationStopped()) {
const subItems: ContextMenuProps[] = [];
subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
+ subItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
}
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 323d7be25..53435ccc9 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -4,16 +4,15 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
-import { ImageField } from "../../../new_fields/URLField";
-import { TraceMobx } from "../../../new_fields/util";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { RichTextField } from "../../../fields/RichTextField";
+import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { ScriptField } from "../../../fields/ScriptField";
+import { NumCast, StrCast, Cast } from "../../../fields/Types";
+import { ImageField } from "../../../fields/URLField";
+import { TraceMobx } from "../../../fields/util";
import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { ContextMenu } from "../ContextMenu";
@@ -22,7 +21,8 @@ import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
import "./CollectionStackingView.scss";
-import { listSpec } from "../../../new_fields/Schema";
+import { listSpec } from "../../../fields/Schema";
+import { SnappingManager } from "../../util/SnappingManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -120,7 +120,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
pointerEntered = () => {
- if (SelectionManager.GetIsDragging()) {
+ if (SnappingManager.GetIsDragging()) {
this._background = "#b4b4b4";
}
}
@@ -322,11 +322,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
title={evContents === `NO ${key.toUpperCase()} VALUE` ?
`Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
- style={{
- width: "100%",
- background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit",
- color: "grey"
- }}>
+ style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit" }}>
<EditableView {...headerEditableViewProps} />
{evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
@@ -359,7 +355,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<div className="collectionStackingViewFieldColumn" key={heading}
style={{
width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
- height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined,
+ height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
background: this._background
}}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index 5b9a69bf7..c5c3f96e8 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -1,7 +1,7 @@
import { CollectionSubView } from "./CollectionSubView";
import React = require("react");
import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast } from "../../../fields/Types";
import "./CollectionStaffView.scss";
import { observer } from "mobx-react";
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 1bfd408f8..c9eb08b45 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,14 +1,14 @@
import { action, computed, IReactionDisposer, reaction } from "mobx";
import { basename } from 'path';
-import CursorField from "../../../new_fields/CursorField";
-import { Doc, Opt } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
+import CursorField from "../../../fields/CursorField";
+import { Doc, Opt } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { listSpec } from "../../../fields/Schema";
+import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, ScriptCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { Upload } from "../../../server/SharedMediaTypes";
import { Utils } from "../../../Utils";
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
@@ -16,7 +16,7 @@ import { DocServer } from "../../DocServer";
import { Docs, DocumentOptions } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { Networking } from "../../Network";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { InteractionUtils } from "../../util/InteractionUtils";
import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -27,9 +27,9 @@ import { CollectionView } from "./CollectionView";
import React = require("react");
export interface CollectionViewProps extends FieldViewProps {
- addDocument: (document: Doc) => boolean;
- removeDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+ addDocument: (document: Doc | Doc[]) => boolean;
+ removeDocument: (document: Doc | Doc[]) => boolean;
+ moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
PanelWidth: () => number;
PanelHeight: () => number;
VisibleHeight?: () => number;
@@ -43,6 +43,10 @@ export interface CollectionViewProps extends FieldViewProps {
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
children?: never | (() => JSX.Element[]) | React.ReactNode;
+ ChildLayoutTemplate?: () => Doc;
+ ChildLayoutString?: string;
+ childClickScript?: ScriptField;
+ childDoubleClickScript?: ScriptField;
freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
@@ -56,7 +60,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private _childLayoutDisposer?: IReactionDisposer;
protected _mainCont?: HTMLDivElement;
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer?.();
@@ -64,7 +67,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.multiTouchDisposer?.();
if (ele) {
this._mainCont = ele;
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
@@ -73,25 +76,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.createDashEventsTarget(ele);
}
- componentDidMount() {
- this._childLayoutDisposer = reaction(() => ({ childDocs: this.childDocs, childLayout: Cast(this.props.Document.childLayout, Doc) }),
- ({ childDocs, childLayout }) => {
- if (childLayout instanceof Doc) {
- childDocs.map(doc => {
- doc.layout_fromParent = childLayout;
- doc.layoutKey = "layout_fromParent";
- });
- }
- else if (!(childLayout instanceof Promise)) {
- childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
- }
- }, { fireImmediately: true });
-
- }
componentWillUnmount() {
this.gestureDisposer?.();
this.multiTouchDisposer?.();
- this._childLayoutDisposer?.();
}
@computed get dataDoc() {
@@ -108,7 +95,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
- return this.dataDoc[this.props.fieldKey + (this.props.annotationsKey ? "-" + this.props.annotationsKey : "")];
+ return this.dataDoc[this.props.annotationsKey || this.props.fieldKey];
}
get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
@@ -208,34 +195,60 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
}
+ protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
+ if (de.complete.docDragData) {
+ // if targetDropAction is, say 'alias', but we're just dragging within a collection, we want to ignore the targetAction.
+ // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
+ if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+ de.complete.docDragData.dropAction = targetAction;
+ }
+ e.stopPropagation();
+ }
+ }
+
+ addDocument = (doc: Doc | Doc[]) => this.props.addDocument(doc);
+
@undoBatch
@action
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
- (this.props.Document.dropConverter instanceof ScriptField) &&
- this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this
+ ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
if (docDragData) {
let added = false;
if (docDragData.dropAction || docDragData.userDropAction) {
- added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+ added = this.addDocument(docDragData.droppedDocuments);
} else if (docDragData.moveDocument) {
- const movedDocs = docDragData.draggedDocuments;
- added = movedDocs.reduce((added: boolean, d, i) =>
- docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
- docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
+ const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
+ const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
+ const res = addedDocs.length ? this.addDocument(addedDocs) : true;
+ added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, this.addDocument) : res;
} else {
- added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+ added = this.addDocument(docDragData.droppedDocuments);
}
e.stopPropagation();
return added;
}
else if (de.complete.annoDragData) {
e.stopPropagation();
- return this.props.addDocument(de.complete.annoDragData.dropDocument);
+ return this.addDocument(de.complete.annoDragData.dropDocument);
}
return false;
}
+ readUploadedFileAsText = (inputFile: File) => {
+ const temporaryFileReader = new FileReader();
+
+ return new Promise((resolve, reject) => {
+ temporaryFileReader.onerror = () => {
+ temporaryFileReader.abort();
+ reject(new DOMException("Problem parsing input file."));
+ };
+ temporaryFileReader.onload = () => {
+ resolve(temporaryFileReader.result);
+ };
+ temporaryFileReader.readAsText(inputFile);
+ });
+ }
@undoBatch
@action
protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
@@ -254,7 +267,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
e.stopPropagation();
e.preventDefault();
- const { addDocument } = this.props;
+ const { addDocument } = this;
if (!addDocument) {
alert("this.props.addDocument does not exist. Aborting drop operation.");
return;
@@ -332,23 +345,23 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}));
return;
}
- let matches: RegExpExecArray | null;
- if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
- const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
- const proto = newBox.proto!;
- const documentId = matches[2];
- proto[GoogleRef] = documentId;
- proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
- proto.backgroundColor = "#eeeeff";
- addDocument(newBox);
- return;
- }
- if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
- const albumId = matches[3];
- const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
- console.log(mediaItems);
- return;
- }
+ // let matches: RegExpExecArray | null;
+ // if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+ // const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
+ // const proto = newBox.proto!;
+ // const documentId = matches[2];
+ // proto[GoogleRef] = documentId;
+ // proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
+ // proto.backgroundColor = "#eeeeff";
+ // addDocument(newBox);
+ // return;
+ // }
+ // if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
+ // const albumId = matches[3];
+ // const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
+ // console.log(mediaItems);
+ // return;
+ // }
}
const { items } = e.dataTransfer;
@@ -373,7 +386,21 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
if (item.kind === "file") {
const file = item.getAsFile();
- file && file.type && files.push(file);
+ file?.type && files.push(file);
+
+ file?.type === "application/json" && this.readUploadedFileAsText(file).then(result => {
+ console.log(result);
+ const json = JSON.parse(result as string);
+ addDocument(Docs.Create.TreeDocument(
+ json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => {
+ const label = Docs.Create.LabelDocument({ title: c["#text"], _width: 120, _height: 20 });
+ const proto = Doc.GetProto(label);
+ proto._width = 120;
+ proto._height = 20;
+ return proto;
+ }
+ ), { _width: 150, _height: 600, title: "across", backgroundColor: "white", _singleLine: true }));
+ });
}
}
for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 045134225..15bc0bfd5 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,11 +1,11 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocCastAsync } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
+import { Doc, Opt, DocCastAsync } from "../../../fields/Doc";
+import { List } from "../../../fields/List";
+import { ObjectField } from "../../../fields/ObjectField";
+import { RichTextField } from "../../../fields/RichTextField";
+import { ComputedField, ScriptField } from "../../../fields/ScriptField";
+import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types";
import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
import { Scripting } from "../../util/Scripting";
import { ContextMenu } from "../ContextMenu";
@@ -28,7 +28,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
@observable _childClickedScript: Opt<ScriptField>;
@observable _viewDefDivClick: Opt<ScriptField>;
async componentDidMount() {
- const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
+ const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
runInAction(() => {
this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
@@ -84,7 +84,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
@computed get contents() {
return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
- <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
+ <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={true} layoutEngine={this.layoutEngine} />
</div>;
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index a00bb6bfb..2aac81146 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -81,7 +81,6 @@
position: relative;
text-overflow: ellipsis;
white-space: pre-wrap;
- overflow: hidden;
min-width: 10px;
// width:100%;//width: max-content;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d938bd7ad..3e99af724 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,21 +1,19 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, untracked } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { List } from '../../../new_fields/List';
-import { RichTextField } from '../../../new_fields/RichTextField';
-import { Document, listSpec } from '../../../new_fields/Schema';
-import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
+import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { PrefetchProxy } from '../../../fields/Proxy';
+import { Document, listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
-import { makeTemplate } from '../../util/DropConverter';
import { Scripting } from '../../util/Scripting';
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
@@ -33,7 +31,8 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import { CollectionViewType } from './CollectionView';
import React = require("react");
-
+import { makeTemplate } from '../../util/DropConverter';
+import { TraceMobx } from '../../../fields/util';
export interface TreeViewProps {
document: Doc;
@@ -42,7 +41,7 @@ export interface TreeViewProps {
containingCollection: Doc;
prevSibling?: Doc;
renderDepth: number;
- deleteDoc: (doc: Doc) => boolean;
+ deleteDoc: (doc: Doc | Doc[]) => boolean;
moveDocument: DragManager.MoveFunction;
dropAction: dropActionType;
addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
@@ -50,7 +49,7 @@ export interface TreeViewProps {
panelWidth: () => number;
panelHeight: () => number;
ChromeHeight: undefined | (() => number);
- addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
+ addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
indentDocument?: () => void;
outdentDocument?: () => void;
ScreenToLocalTransform: () => Transform;
@@ -61,24 +60,12 @@ export interface TreeViewProps {
active: (outsideReaction?: boolean) => boolean;
treeViewHideHeaderFields: () => boolean;
treeViewPreventOpen: boolean;
- renderedIds: string[];
+ renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
onCheckedClick?: ScriptField;
onChildClick?: ScriptField;
ignoreFields?: string[];
}
-library.add(faTrashAlt);
-library.add(faAngleRight);
-library.add(faBell);
-library.add(faTrash);
-library.add(faCamera);
-library.add(faExpand);
-library.add(faCaretDown);
-library.add(faCaretRight);
-library.add(faCaretSquareDown);
-library.add(faCaretSquareRight);
-library.add(faArrowsAltH);
-library.add(faPlus, faMinus);
@observer
/**
* Renders a treeView of a collection of documents
@@ -89,66 +76,59 @@ library.add(faPlus, faMinus);
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
+ static _editTitleScript: ScriptField | undefined;
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
private _tref = React.createRef<HTMLDivElement>();
+ private _docRef = React.createRef<DocumentView>();
get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive
-
get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
@observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
- @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
+ @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.props.document.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
@computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
- @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
+ @computed get dataDoc() { return this.props.document[DataSym]; }
@computed get fieldKey() {
const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'");
return splits.length > 1 ? splits[1].split("\'")[0] : "data";
}
childDocList(field: string) {
const layout = Doc.LayoutField(this.props.document) instanceof Doc ? Doc.LayoutField(this.props.document) as Doc : undefined;
- return ((this.props.dataDoc ? Cast(this.props.dataDoc[field], listSpec(Doc)) : undefined) ||
- (layout ? Cast(layout[field], listSpec(Doc)) : undefined) ||
- Cast(this.props.document[field], listSpec(Doc))) as Doc[];
+ return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
+ (layout ? Cast(layout[field], listSpec(Doc)) : undefined) || // else if there's a layout doc, display it's fields
+ Cast(this.props.document[field], listSpec(Doc))) as Doc[]; // otherwise use the document's data field
}
@computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
- @computed get templateDataDoc() {
- if (this.props.dataDoc === undefined && Doc.LayoutField(this.props.document) !== "string") {
- // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string),
- // then we render the layout document as a template and use this document as the data context for the template layout.
- return this.props.document;
- }
- return this.props.dataDoc;
- }
@computed get boundsOfCollectionDocument() {
return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 || !DocListCast(this.props.document[this.fieldKey]).length ? undefined :
Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
}
- @undoBatch delete = () => this.props.deleteDoc(this.props.document);
- @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath);
- @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
- @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => {
+ @undoBatch openRight = () => this.props.addDocTab(this.props.document, "onRight", this.props.libraryPath);
+ @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
}
- @undoBatch @action remove = (document: Document, key: string) => {
- return Doc.RemoveDocFromList(this.dataDoc, key, document);
+ @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
+ flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
}
- @undoBatch @action removeDoc = (document: Document) => {
- return Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), document);
+ @undoBatch @action removeDoc = (doc: Doc | Doc[]) => {
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
+ flg && Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), doc), true);
}
protected createTreeDropTarget = (ele: HTMLDivElement) => {
- this._treedropDisposer && this._treedropDisposer();
+ this._treedropDisposer?.();
ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)), this.props.document);
}
onPointerEnter = (e: React.PointerEvent): void => {
this.props.active(true) && Doc.BrushDoc(this.dataDoc);
- if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+ if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
this._header!.current!.className = "treeViewItem-header";
document.addEventListener("pointermove", this.onDragMove, true);
}
@@ -186,7 +166,7 @@ class TreeView extends React.Component<TreeViewProps> {
})}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.props.document, key, value, false);
- const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
Doc.SetInPlace(this.props.document, "editTitle", undefined, false);
Doc.SetInPlace(doc, "editTitle", true, false);
return this.props.addDocument(doc);
@@ -222,9 +202,10 @@ class TreeView extends React.Component<TreeViewProps> {
if (de.complete.docDragData) {
e.stopPropagation();
if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
- let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before);
+ let addDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
if (inside) {
- addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
+ addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce(
+ ((flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc)), true) || addDoc(doc);
}
const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
const move = de.complete.docDragData.dropAction === "move" || de.complete.docDragData.dropAction;
@@ -284,8 +265,9 @@ class TreeView extends React.Component<TreeViewProps> {
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) {
- const remDoc = (doc: Doc) => this.remove(doc, key);
- const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+ const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
+ const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce(
+ (flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true);
contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
@@ -325,10 +307,12 @@ class TreeView extends React.Component<TreeViewProps> {
rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
@computed get renderContent() {
+ TraceMobx();
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
- const remDoc = (doc: Doc) => this.remove(doc, expandKey);
- const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true);
+ const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey);
+ const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) =>
+ (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true);
const docs = expandKey === "links" ? this.childLinks : this.childDocs;
const sortKey = `${this.fieldKey}-sortAscending`;
return <ul key={expandKey + "more"} onClick={(e) => {
@@ -337,7 +321,7 @@ class TreeView extends React.Component<TreeViewProps> {
}}>
{!docs ? (null) :
TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
- this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
+ this.dataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
StrCast(this.props.document.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
[...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)}
@@ -353,27 +337,31 @@ class TreeView extends React.Component<TreeViewProps> {
return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}>
<ContentFittingDocumentView
Document={layoutDoc}
- DataDocument={this.templateDataDoc}
+ DataDoc={this.dataDoc}
LibraryPath={emptyPath}
renderDepth={this.props.renderDepth + 1}
rootSelected={returnTrue}
backgroundColor={this.props.backgroundColor}
fitToBox={this.boundsOfCollectionDocument !== undefined}
FreezeDimensions={true}
- NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
- NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+ NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : returnZero}
+ NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : returnZero}
PanelWidth={panelWidth}
PanelHeight={panelHeight}
- getTransform={this.docTransform}
- CollectionDoc={this.props.containingCollection}
- CollectionView={undefined}
+ focus={returnFalse}
+ ScreenToLocalTransform={this.docTransform}
+ ContainingCollectionDoc={this.props.containingCollection}
+ ContainingCollectionView={undefined}
addDocument={returnFalse}
moveDocument={this.props.moveDocument}
removeDocument={returnFalse}
- active={this.props.active}
+ parentActive={this.props.active}
whenActiveChanged={emptyFunction}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres} />
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
+ />
</div>;
}
}
@@ -399,13 +387,16 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
const checked = this.props.document.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
- return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}>
+ return <div className="bullet"
+ title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
+ onClick={this.bulletClick}
+ style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}>
{<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
</div>;
}
showContextMenu = (e: React.MouseEvent) => {
- simulateMouseClick(this._docRef.current!.ContentDiv!, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+ this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
e.stopPropagation();
}
focusOnDoc = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(doc)?.props.focus(doc, true);
@@ -413,15 +404,13 @@ class TreeView extends React.Component<TreeViewProps> {
const focusScript = ScriptField.MakeFunction(`DocFocus(self)`);
return [{ script: focusScript!, label: "Focus" }];
}
- _docRef = React.createRef<DocumentView>();
/**
* Renders the EditableView title element for placement into the tree.
*/
@computed
get renderTitle() {
- const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
- const editTitle = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)");
-
+ TraceMobx();
+ (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(self, 'editTitle', true)"));
const headerElements = (
<>
<FontAwesomeIcon icon="cog" size="sm" onClick={e => this.showContextMenu(e)}></FontAwesomeIcon>
@@ -442,13 +431,12 @@ class TreeView extends React.Component<TreeViewProps> {
<FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" />
</div>);
return <>
- <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} onPointerDown={onItemDown}
+ <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
style={{
- background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
fontWeight: this.props.document.searchMatch ? "bold" : undefined,
textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
- pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? undefined : "none"
+ pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none"
}} >
{Doc.GetT(this.props.document, "editTitle", "boolean", true) ?
this.editableView("title") :
@@ -456,12 +444,13 @@ class TreeView extends React.Component<TreeViewProps> {
ref={this._docRef}
Document={this.props.document}
DataDoc={undefined}
- LibraryPath={this.props.libraryPath || []}
+ treeViewId={this.props.treeViewId[Id]}
+ LibraryPath={this.props.libraryPath || emptyPath}
addDocument={undefined}
addDocTab={this.props.addDocTab}
rootSelected={returnTrue}
pinToPres={emptyFunction}
- onClick={this.props.onChildClick || editTitle}
+ onClick={this.props.onChildClick || TreeView._editTitleScript}
dropAction={this.props.dropAction}
moveDocument={this.move}
removeDocument={this.removeDoc}
@@ -473,11 +462,11 @@ class TreeView extends React.Component<TreeViewProps> {
NativeWidth={returnZero}
contextMenuItems={this.contextMenuItems}
renderDepth={1}
- focus={emptyFunction}
+ focus={returnTrue}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
- dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)}
+ dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)}
ContainingCollectionView={undefined}
ContainingCollectionDoc={this.props.containingCollection}
/>}
@@ -488,8 +477,9 @@ class TreeView extends React.Component<TreeViewProps> {
}
render() {
+ TraceMobx();
const sorting = this.props.document[`${this.fieldKey}-sortAscending`];
- setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
+ //setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
return <div className="treeViewItem-container" ref={this.createTreeDropTarget}>
<li className="collection-child">
<div className="treeViewItem-header" ref={this._header} onClick={e => {
@@ -497,12 +487,14 @@ class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
e.preventDefault();
}
- }} onPointerDown={e => {
- if (this.props.active(true)) {
- e.stopPropagation();
- e.preventDefault();
- }
- }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ }}
+ onPointerDown={e => {
+ if (this.props.active(true)) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
{this.renderBullet}
{this.renderTitle}
</div>
@@ -520,8 +512,8 @@ class TreeView extends React.Component<TreeViewProps> {
key: string,
parentCollectionDoc: Doc | undefined,
parentPrevSibling: Doc | undefined,
- add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
- remove: ((doc: Doc) => boolean),
+ add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean,
+ remove: ((doc: Doc | Doc[]) => boolean),
move: DragManager.MoveFunction,
dropAction: dropActionType,
addDocTab: (doc: Doc, where: string) => boolean,
@@ -610,7 +602,7 @@ class TreeView extends React.Component<TreeViewProps> {
remove(child);
}
};
- const addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
+ const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => {
return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false);
};
const childLayout = Doc.Layout(pair.layout);
@@ -680,22 +672,26 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
@action
- remove = (document: Document): boolean => {
- const children = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
- if (children.indexOf(document) !== -1) {
- children.splice(children.indexOf(document), 1);
+ remove = (doc: Doc | Doc[]): boolean => {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ const targetDataDoc = this.props.Document[DataSym];
+ const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+ const result = value.filter(v => !docs.includes(v));
+ if (result.length !== value.length) {
+ targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
return true;
}
return false;
}
@action
- addDoc = (doc: Document, relativeTo: Opt<Doc>, before?: boolean): boolean => {
- const doAddDoc = () =>
- Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false);
+ addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
+ const doAddDoc = (doc: Doc | Doc[]) =>
+ (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
+ flg && Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true);
if (this.props.Document.resolvedDataDoc instanceof Promise) {
- this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc());
+ this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc(doc));
} else {
- doAddDoc();
+ doAddDoc(doc);
}
return true;
}
@@ -721,14 +717,6 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
ContextMenu.Instance.addItem({
description: "Buxton Layout", icon: "eye", event: () => {
- DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
- DocListCast(d.data).map((img, i) => {
- const caption = (d.captions as any)[i];
- if (caption) {
- Doc.GetProto(img).caption = caption;
- }
- });
- });
const { ImageDocument } = Docs.Create;
const { Document } = this.props;
const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
@@ -738,22 +726,27 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
heroView._showTitle = "title";
heroView._showTitleHover = "titlehover";
- Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data",
- Docs.Create.FontIconDocument({
- title: "hero view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
- dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), icon: "portrait",
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
- }));
-
- Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data",
- Docs.Create.FontIconDocument({
- title: "detail view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
- dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), icon: "file-alt",
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
- }));
-
- Document.childLayout = heroView;
- Document.childDetailView = detailView;
+ const doubleClickView = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 }); // replace with desired double click target
+ DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
+ DocListCast(d.data).map((img, i) => {
+ const caption = (d.captions as any)[i];
+ if (caption) {
+ Doc.GetProto(img).caption = caption;
+ Doc.GetProto(img).doubleClickView = doubleClickView;
+ }
+ });
+ Doc.GetProto(d).type = "buxton";
+ Doc.GetProto(d).proto = heroView; // all devices "are" heroViews that share the same layout & defaults. Seems better than making them all be independent and copy a layout string // .layout = ImageBox.LayoutString("hero");
+ });
+
+ const iconBuxtonView = ImageDocument(fallbackImg, { title: "hero", _width: 60, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") });
+ iconBuxtonView.isTemplateDoc = makeTemplate(iconBuxtonView, true, "icon_buxton");
+ Doc.UserDoc()["template-icon-view-buxton"] = new PrefetchProxy(iconBuxtonView);
+ const tempIcons = Doc.GetProto(Cast(Doc.UserDoc()["template-icons"], Doc, null));
+ Doc.AddDocToList(tempIcons, "data", iconBuxtonView);
+
+ Document.childLayoutTemplate = heroView;
+ Document.childClickedOpenTemplateView = new PrefetchProxy(detailView);
Document._viewType = CollectionViewType.Time;
Document._forceActive = true;
Document._pivotField = "company";
@@ -774,16 +767,19 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
return <div id="toolbar" key="toolbar">
<button className="toolbar-button round-button" title="Empty"
onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
- <FontAwesomeIcon icon={faTrash} size="sm" />
+ <FontAwesomeIcon icon={"trash"} size="sm" />
</button>
</div >;
}
+ onKeyPress = (e: React.KeyboardEvent) => {
+ console.log(e);
+ }
render() {
if (!(this.props.Document instanceof Doc)) return (null);
const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType;
- const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
- const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
+ const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
+ const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(d, target, addDoc);
const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
return !childDocs ? (null) : (
<div className="collectionTreeView-dropTarget" id="body"
@@ -793,6 +789,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`,
paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`
}}
+ onKeyPress={this.onKeyPress}
onContextMenu={this.onContextMenu}
onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
@@ -807,7 +804,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
OnFillDown={undoBatch((value: string) => {
Doc.SetInPlace(this.dataDoc, "title", value, false);
- const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
EditableView.loadId = doc[Id];
Doc.SetInPlace(doc, "editTitle", true, false);
this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index d43dd387a..7877fe155 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -70,7 +70,7 @@
height: 30px;
position: absolute;
bottom: 15px;
- left: 15px;
+ right: 15px;
border: 2px solid black;
border-radius: 50%;
padding: 3px;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 801704673..3b2e5e4fc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,17 +7,15 @@ import { observer } from "mobx-react";
import * as React from 'react';
import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
-import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast, Field, Opt } from '../../../new_fields/Doc';
-import { List } from '../../../new_fields/List';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
-import { ImageField } from '../../../new_fields/URLField';
-import { TraceMobx } from '../../../new_fields/util';
+import { DateField } from '../../../fields/DateField';
+import { DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { ScriptBox } from '../ScriptBox';
@@ -37,15 +35,14 @@ import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { listSpec } from '../../../new_fields/Schema';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { Id } from '../../../fields/FieldSymbols';
+import { listSpec } from '../../../fields/Schema';
import { Docs } from '../../documents/Documents';
-import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
+import { ScriptField, ComputedField } from '../../../fields/ScriptField';
import { InteractionUtils } from '../../util/InteractionUtils';
-import { ObjectField } from '../../../new_fields/ObjectField';
+import { ObjectField } from '../../../fields/ObjectField';
import CollectionMapView from './CollectionMapView';
-import { Transform } from 'prosemirror-transform';
import { CollectionPileView } from './CollectionPileView';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -71,18 +68,25 @@ export enum CollectionViewType {
Map = "map",
Pile = "pileup"
}
+export interface CollectionViewCustomProps {
+ filterAddDocument: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+ childLayoutTemplate?: () => Opt<Doc>; // specify a layout Doc template to use for children of the collection
+ childLayoutString?: string; // specify a layout string to use for children of the collection
+}
export interface CollectionRenderProps {
- addDocument: (document: Doc) => boolean;
- removeDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+ addDocument: (document: Doc | Doc[]) => boolean;
+ removeDocument: (document: Doc | Doc[]) => boolean;
+ moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
PanelWidth: () => number;
+ ChildLayoutTemplate?: () => Doc;
+ ChildLayoutString?: string;
}
@observer
-export class CollectionView extends Touchable<FieldViewProps> {
+export class CollectionView extends Touchable<FieldViewProps & CollectionViewCustomProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
private _isChildActive = false; //TODO should this be observable?
@@ -112,31 +116,33 @@ export class CollectionView extends Touchable<FieldViewProps> {
whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
@action.bound
- addDocument(doc: Doc): boolean {
+ addDocument = (doc: Doc | Doc[]): boolean => {
+ if (doc instanceof Doc) {
+ if (this.props.filterAddDocument?.(doc) === false) {
+ return false;
+ }
+ }
+ const docs = doc instanceof Doc ? [doc] : doc;
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
- !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
- // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
- doc.context = this.props.Document;
- targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- Doc.GetProto(doc).lastOpened = new DateField;
+ const added = docs.filter(d => !docList.includes(d));
+ if (added.length) {
+ added.map(doc => doc.context = this.props.Document);
+ added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
+ targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
return true;
}
@action.bound
- removeDocument(doc: Doc): boolean {
+ removeDocument = (doc: any): boolean => {
+ const docs = doc instanceof Doc ? [doc] : doc as Doc[];
const targetDataDoc = this.props.Document[DataSym];
- const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
- docView && SelectionManager.DeselectDoc(docView);
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
- let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
- index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
-
- doc.context = undefined;
- ContextMenu.Instance?.clearItems();
- if (index !== -1) {
- value.splice(index, 1);
- targetDataDoc[this.props.fieldKey] = new List<Doc>(value);
+ const result = value.filter(v => !docs.includes(v));
+ if (result.length !== value.length) {
+ targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
return true;
}
return false;
@@ -147,7 +153,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
// otherwise, the document being moved must be able to be removed from its container before
// moving it into the target.
@action.bound
- moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
return true;
}
@@ -155,10 +161,14 @@ export class CollectionView extends Touchable<FieldViewProps> {
}
showIsTagged = () => {
- const children = DocListCast(this.props.Document[this.props.fieldKey]);
- const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
- const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
- return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
+ return (null);
+ // this section would display an icon in the bototm right of a collection to indicate that all
+ // photos had been processed through Google's content analysis API and Google's tags had been
+ // assigned to the documents googlePhotosTags field.
+ // const children = DocListCast(this.props.Document[this.props.fieldKey]);
+ // const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
+ // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
+ // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
}
private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
@@ -190,8 +200,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
// currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
- <CollectionViewBaseChrome CollectionView={this} key="chrome" PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
- return [chrome, this.SubViewHelper(type, renderProps)];
+ <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
+ return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
}
@@ -224,7 +234,6 @@ export class CollectionView extends Touchable<FieldViewProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
this.setupViewTypes("Add a Perspective...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
@@ -238,8 +247,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
if (this.props.Document.childLayout instanceof Doc) {
layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
}
- if (this.props.Document.childDetailView instanceof Doc) {
- layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailView as Doc, "onRight"), icon: "project-diagram" });
+ if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
+ layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
}
layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
@@ -247,15 +256,20 @@ export class CollectionView extends Touchable<FieldViewProps> {
const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }];
- DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick =>
- funcs.push({ key: "onChildClick", name: StrCast(childClick.title), script: ScriptCast(childClick.script) }));
+ const funcs = [
+ { key: "onChildClick", name: "On Child Clicked" },
+ { key: "onChildDoubleClick", name: "On Child Double Clicked" }];
funcs.map(func => onClicks.push({
description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
- func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script));
ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
}
}));
+ DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
+ onClicks.push({
+ description: `Set child ${childClick.title}`,
+ icon: "edit",
+ event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
+ }));
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
const more = ContextMenu.Instance.findByDescription("More...");
@@ -462,6 +476,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
</div>
</div>;
}
+ childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
+ childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString);
render() {
TraceMobx();
@@ -471,7 +487,9 @@ export class CollectionView extends Touchable<FieldViewProps> {
moveDocument: this.moveDocument,
active: this.active,
whenActiveChanged: this.whenActiveChanged,
- PanelWidth: this.bodyPanelWidth
+ PanelWidth: this.bodyPanelWidth,
+ ChildLayoutTemplate: this.childLayoutTemplate,
+ ChildLayoutString: this.childLayoutString,
};
return (<div className={"collectionView"}
style={{
@@ -481,7 +499,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
}}
onContextMenu={this.onContextMenu}>
{this.showIsTagged()}
- <div style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
+ <div className="collectionView-facetCont" style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
</div>
{this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index d26e3a38b..5dc0b09ac 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -2,11 +2,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { listSpec } from "../../../fields/Schema";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
import { DragManager } from "../../util/DragManager";
import { undoBatch } from "../../util/UndoManager";
@@ -44,9 +44,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
initialize: emptyFunction,
};
_narrativeCommand = {
- params: ["target", "source"], title: "=> click item view",
- script: "this.target.childDetailView = getDocTemplate(this.source?.[0])",
- immediate: (source: Doc[]) => this.target.childDetailView = Doc.getDocTemplate(source?.[0]),
+ params: ["target", "source"], title: "=> click clicked open view",
+ script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+ immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
_contentCommand = {
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 10c6ead1a..649406e6c 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -1,12 +1,12 @@
import * as React from "react";
import './ParentDocumentSelector.scss';
-import { Doc } from "../../../new_fields/Doc";
+import { Doc } from "../../../fields/Doc";
import { observer } from "mobx-react";
import { observable, action, runInAction, trace, computed, reaction, IReactionDisposer } from "mobx";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { Id } from "../../../fields/FieldSymbols";
import { SearchUtil } from "../../util/SearchUtil";
import { CollectionDockingView } from "./CollectionDockingView";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { NumCast, StrCast } from "../../../fields/Types";
import { CollectionViewType } from "./CollectionView";
import { DocumentButtonBar } from "../DocumentButtonBar";
import { DocumentManager } from "../../util/DocumentManager";
@@ -94,7 +94,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> {
}
@observer
-export class DockingViewButtonSelector extends React.Component<{ views: DocumentView[], Stack: any }> {
+export class DockingViewButtonSelector extends React.Component<{ views: () => DocumentView[], Stack: any }> {
customStylesheet(styles: any) {
return {
...styles,
@@ -120,7 +120,7 @@ export class DockingViewButtonSelector extends React.Component<{ views: Document
if (getComputedStyle(this._ref.current!).width !== "100%") {
e.stopPropagation(); e.preventDefault();
}
- this.props.views[0]?.select(false);
+ this.props.views()[0]?.select(false);
}} className="buttonSelector">
<Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
<FontAwesomeIcon icon={"cog"} size={"sm"} />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9a864078a..3860ce2d7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,15 +1,15 @@
-import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc";
-import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
+import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../fields/Doc";
+import { NumCast, StrCast, Cast } from "../../../../fields/Types";
import { ScriptBox } from "../../ScriptBox";
import { CompileScript } from "../../../util/Scripting";
-import { ScriptField } from "../../../../new_fields/ScriptField";
+import { ScriptField } from "../../../../fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { emptyFunction, aggregateBounds } from "../../../../Utils";
import React = require("react");
-import { Id, ToString } from "../../../../new_fields/FieldSymbols";
-import { ObjectField } from "../../../../new_fields/ObjectField";
-import { RefField } from "../../../../new_fields/RefField";
-import { listSpec } from "../../../../new_fields/Schema";
+import { Id, ToString } from "../../../../fields/FieldSymbols";
+import { ObjectField } from "../../../../fields/ObjectField";
+import { RefField } from "../../../../fields/RefField";
+import { listSpec } from "../../../../fields/Schema";
export interface ViewDefBounds {
type: string;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index cf12ef382..f3fc04752 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
-import { Doc } from "../../../../new_fields/Doc";
+import { Doc } from "../../../../fields/Doc";
import { Utils } from '../../../../Utils';
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
@@ -7,8 +7,9 @@ import React = require("react");
import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
import { observable, action, reaction, IReactionDisposer } from "mobx";
-import { StrCast } from "../../../../new_fields/Types";
-import { Id } from "../../../../new_fields/FieldSymbols";
+import { StrCast, Cast } from "../../../../fields/Types";
+import { Id } from "../../../../fields/FieldSymbols";
+import { SnappingManager } from "../../../util/SnappingManager";
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -24,6 +25,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
componentDidMount() {
this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
+ if (SnappingManager.GetIsDragging()) return;
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
@@ -40,7 +42,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height,
abounds.left, abounds.top, abounds.width, abounds.height,
apt.point.x, apt.point.y);
- const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
+ const afield = this.props.A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
// really hacky stuff to make the LinkAnchorBox display where we want it to:
@@ -79,11 +81,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
componentWillUnmount() {
this._anchorDisposer?.();
}
-
render() {
+ if (SnappingManager.GetIsDragging()) return null;
this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+ const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
@@ -105,15 +107,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
const text = StrCast(this.props.A.props.Document.linkRelationship);
return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <text x={(pt1[0] + pt2[0]) / 2} y={(pt1[1] + pt2[1]) / 2}>
+ <text x={(Math.min(pt1[0], pt2[0]) * 2 + Math.max(pt1[0], pt2[0])) / 3} y={(pt1[1] + pt2[1]) / 2}>
{text !== "-ungrouped-" ? text : ""}
</text>
<path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
- {/* <line key="linkLine" className="collectionfreeformlinkview-linkLine"
- style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
- x1={`${pt1[0]}`} y1={`${pt1[1]}`}
- x2={`${pt2[0]}`} y2={`${pt2[1]}`} /> */}
</>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 4b5e977df..ae81b4b36 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,15 +1,15 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../../new_fields/Doc";
-import { Id } from "../../../../new_fields/FieldSymbols";
+import { Doc } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
import { DocumentManager } from "../../../util/DocumentManager";
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
import { Utils, emptyFunction } from "../../../../Utils";
-import { SelectionManager } from "../../../util/SelectionManager";
import { DocumentType } from "../../../documents/DocumentTypes";
+import { SnappingManager } from "../../../util/SnappingManager";
@observer
export class CollectionFreeFormLinksView extends React.Component {
@@ -30,13 +30,13 @@ export class CollectionFreeFormLinksView extends React.Component {
return drawnPairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]);
return connections.filter(c =>
- c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK &&
- c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed
+ c.a.props.Document.type === DocumentType.LINK &&
+ c.a.props.pinToPres !== emptyFunction && c.b.props.pinToPres !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed
).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
}
render() {
- return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
+ return SnappingManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
<svg className="collectionfreeformlinksview-svgCanvas">
{this.uniqueConnections}
</svg>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 92fa2781c..548ad78a5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,16 +1,16 @@
import { observer } from "mobx-react";
import * as mobxUtils from 'mobx-utils';
-import CursorField from "../../../../new_fields/CursorField";
-import { listSpec } from "../../../../new_fields/Schema";
-import { Cast } from "../../../../new_fields/Types";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import CursorField from "../../../../fields/CursorField";
+import { listSpec } from "../../../../fields/Schema";
+import { Cast } from "../../../../fields/Types";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import { CollectionViewProps } from "../CollectionSubView";
import "./CollectionFreeFormView.scss";
import React = require("react");
import v5 = require("uuid/v5");
import { computed } from "mobx";
-import { FieldResult } from "../../../../new_fields/Doc";
-import { List } from "../../../../new_fields/List";
+import { FieldResult } from "../../../../fields/Doc";
+import { List } from "../../../../fields/List";
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d85233041..bf679309c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,21 +1,21 @@
import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
-import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
-import { List } from "../../../../new_fields/List";
-import { RichTextField } from "../../../../new_fields/RichTextField";
-import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
-import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
-import { TraceMobx } from "../../../../new_fields/util";
+import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
+import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas";
+import { Id } from "../../../../fields/FieldSymbols";
+import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField";
+import { List } from "../../../../fields/List";
+import { RichTextField } from "../../../../fields/RichTextField";
+import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
+import { ScriptField, ComputedField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { TraceMobx } from "../../../../fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
@@ -45,6 +45,7 @@ import { MarqueeView } from "./MarqueeView";
import React = require("react");
import { CollectionViewType } from "../CollectionView";
import { Timeline } from "../../animationtimeline/Timeline";
+import { SnappingManager } from "../../../util/SnappingManager";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -66,11 +67,10 @@ export const panZoomSchema = createSchema({
fitH: "number"
});
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
-const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
- childClickScript?: ScriptField;
viewDefDivClick?: ScriptField;
};
@@ -113,8 +113,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
- private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
- private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+ private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections
+ private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
@@ -123,11 +123,27 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox);
}
- private addDocument = (newBox: Doc) => {
- const added = this.props.addDocument(newBox);
- added && this.bringToFront(newBox);
- added && this.updateCluster(newBox);
- return added;
+ addDocument = (newBox: Doc | Doc[]) => {
+ const timecode = Cast(this.props.Document.timecode, "number", null);
+ if (timecode !== undefined) {
+ ((newBox instanceof Doc) ? [newBox] : newBox).map(doc => {
+ doc["x-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.x)));
+ doc["y-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.y)));
+ doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document });
+ doc.x = ComputedField.MakeInterpolated("x", "timecode");
+ doc.y = ComputedField.MakeInterpolated("y", "timecode");
+ });
+ }
+
+ if (newBox instanceof Doc) {
+ const added = this.props.addDocument(newBox);
+ added && this.bringToFront(newBox);
+ added && this.updateCluster(newBox);
+ return added;
+ } else {
+ return this.props.addDocument(newBox);
+ // bcz: deal with clusters
+ }
}
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
@@ -153,6 +169,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const xfo = this.getTransformOverlay();
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
+ const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
if (super.onInternalDrop(e, de)) {
if (de.complete.docDragData) {
if (de.complete.docDragData.droppedDocuments.length) {
@@ -162,20 +179,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
const dropX = NumCast(firstDoc.x);
const dropY = NumCast(firstDoc.y);
- de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => {
- const layoutDoc = Doc.Layout(d);
- d.x = x + NumCast(d.x) - dropX;
- d.y = y + NumCast(d.y) - dropY;
- if (!NumCast(layoutDoc._width)) {
- layoutDoc._width = 300;
- }
- if (!NumCast(layoutDoc._height)) {
- const nw = NumCast(layoutDoc._nativeWidth);
- const nh = NumCast(layoutDoc._nativeHeight);
- layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
+ const droppedDocs = de.complete.docDragData.droppedDocuments;
+ runInAction(() => {
+ zsorted.forEach((doc, index) => doc.zIndex = index + 1);
+ for (let i = 0; i < droppedDocs.length; i++) {
+ const d = droppedDocs[i];
+ const layoutDoc = Doc.Layout(d);
+ d.x = x + NumCast(d.x) - dropX;
+ d.y = y + NumCast(d.y) - dropY;
+ if (!NumCast(layoutDoc._width)) {
+ layoutDoc._width = 300;
+ }
+ if (!NumCast(layoutDoc._height)) {
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
+ layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
+ }
+ d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
- d.isBackground === undefined && this.bringToFront(d);
- }));
+ });
(de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
}
@@ -328,6 +350,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+
+ // if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) {
+ // this.props.addDocTab(this.props.Document, "replace");
+ // e.stopPropagation();
+ // e.preventDefault();
+ // return;
+ // }
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
@@ -383,7 +412,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+ const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, InkingControl.Instance.selectedWidth, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -552,7 +581,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
document.removeEventListener("pointerup", this.onPointerUp);
return;
}
- this.pan(e);
+ (!MarqueeView.DragMarquee || e.altKey) && this.pan(e);
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
@@ -698,7 +727,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
- let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
+ let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
}
@@ -706,7 +735,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- if (localTransform.Scale >= 0.15) {
+ if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
this.props.Document.scale = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
@@ -721,7 +750,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
else if (this.props.active(true)) {
e.stopPropagation();
- this.zoom(e.clientX, e.clientY, e.deltaY);
+ if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
+ else this.zoom(e.clientX, e.clientY, e.deltaY);
}
this.props.Document.targetScale = NumCast(this.props.Document.scale);
}
@@ -760,20 +790,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
- bringToFront = (doc: Doc, sendToBack?: boolean) => {
+ bringToFront = action((doc: Doc, sendToBack?: boolean) => {
if (sendToBack || doc.isBackground) {
doc.zIndex = 0;
}
else {
const docs = this.childLayoutPairs.map(pair => pair.layout);
- docs.slice().sort((doc1, doc2) => {
- if (doc1 === doc) return 1;
- if (doc2 === doc) return -1;
- return NumCast(doc1.zIndex) - NumCast(doc2.zIndex);
- }).forEach((doc, index) => doc.zIndex = index + 1);
- doc.zIndex = docs.length + 1;
+ docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1;
+ if (zlast - docs.length > 100) {
+ for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+ zlast = docs.length + 1;
+ }
+ doc.zIndex = zlast + 1;
}
- }
+ });
scaleAtPt(docpt: number[], scale: number) {
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -855,9 +886,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
@computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- backgroundHalo = () => BoolCast(this.Document.useClusters);
+ @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
@computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
- parentActive = () => this.props.active() || this.backgroundActive ? true : false;
+ backgroundHalo = () => BoolCast(this.Document.useClusters);
+ parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
...this.props,
@@ -867,12 +899,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
DataDoc: childData,
Document: childLayout,
LibraryPath: this.libraryPath,
+ LayoutTemplate: this.props.ChildLayoutTemplate,
+ LayoutTemplateString: this.props.ChildLayoutString,
FreezeDimensions: this.props.freezeChildDimensions,
layoutKey: undefined,
+ setupDragLines: this.setupDragLines,
+ dontRegisterView: this.props.dontRegisterView,
rootSelected: childData ? this.rootSelected : returnFalse,
dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
- //onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
onClick: this.onChildClickHandler,
+ onDoubleClick: this.onChildDoubleClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
PanelWidth: childLayout[WidthSym],
@@ -891,14 +927,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
addDocTab = action((doc: Doc, where: string) => {
if (where === "inParent") {
- const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
- doc.x = pt[0];
- doc.y = pt[1];
- this.props.addDocument(doc);
- return true;
+ if (doc instanceof Doc) {
+ const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = pt[0];
+ doc.y = pt[1];
+ return this.props.addDocument(doc);
+ } else {
+ (doc as any as Doc[]).forEach(doc => {
+ const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = pt[0];
+ doc.y = pt[1];
+ });
+ return this.props.addDocument(doc);
+ }
}
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
- this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
return true;
}
return this.props.addDocTab(doc, where);
@@ -912,6 +956,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const { x, y, z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
+ transition: StrCast(layoutDoc.transition),
width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -996,11 +1041,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
- childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
get doLayoutComputation() {
const { newPool, computedElementData } = this.doInternalLayoutComputation;
- runInAction(() =>
- Array.from(newPool.entries()).map(entry => {
+ const array = Array.from(newPool.entries());
+ runInAction(() => {
+ for (const entry of array) {
const lastPos = this._cachedPool.get(entry[0]); // last computed pos
const newPos = entry[1];
if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
@@ -1009,7 +1054,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
}
- }));
+ }
+ });
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements: ViewDefResult[] = computedElementData.slice();
@@ -1022,7 +1068,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
replica={entry[1].replica}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
- LayoutDoc={this.childLayoutDocFunc}
pointerEvents={
this.backgroundActive ?
true :
@@ -1035,11 +1080,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
}));
+ if (this.props.isAnnotationOverlay) {
+ this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale));
+ }
+
return elements;
}
+ @action
componentDidMount() {
- super.componentDidMount();
+ super.componentDidMount?.();
this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
@@ -1056,12 +1106,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
promoteCollection = undoBatch(action(() => {
- this.childDocs.forEach(doc => {
+ const childDocs = this.childDocs.slice();
+ childDocs.forEach(doc => {
const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = scr?.[0];
doc.y = scr?.[1];
- this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
});
+ this.props.addDocTab(childDocs as any as Doc, "inParent");
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
}));
layoutDocsInGrid = () => {
@@ -1085,23 +1136,69 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
});
}, "arrange contents");
}
+ @undoBatch
+ @action
+ toggleNativeDimensions = () => {
+ Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+ }
+
+ @undoBatch
+ @action
+ snaphsotInterpolated = (): void => {
+ if (this.props.Document.timecode === undefined) {
+ this.childDocs.map(doc => {
+ this.props.Document.timecode = 0;
+ doc["x-indexed"] = new List<number>([NumCast(doc.x)]);
+ doc["y-indexed"] = new List<number>([NumCast(doc.y)]);
+ doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document });
+ doc.x = ComputedField.MakeInterpolated("x", "timecode");
+ doc.y = ComputedField.MakeInterpolated("y", "timecode");
+ });
+ }
+ const timecode = NumCast(this.props.Document.timecode);
+ this.childDocs.map(doc => {
+ const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
+ const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
+ xindexed.length <= timecode + 1 && xindexed.push(NumCast(doc.x));
+ yindexed.length <= timecode + 1 && yindexed.push(NumCast(doc.y));
+ });
+ this.childDocs.map(doc => doc.transition = "transform 1s");
+ this.props.Document.timecode = Math.max(0, timecode + 1);
+ setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010);
+ }
+ @undoBatch
+ @action
+ backupInterpolated = (): void => {
+ this.childDocs.map(doc => doc.transition = "transform 1s");
+ this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
+ setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010);
+ }
+
private thumbIdentifier?: number;
onContextMenu = (e: React.MouseEvent) => {
- if (this.props.children && this.props.annotationsKey) return;
+ if (this.props.annotationsKey) return;
+
+ ContextMenu.Instance.addItem({
+ description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
+ this._timelineVisible = !this._timelineVisible;
+ }), icon: this._timelineVisible ? faEyeSlash : faEye
+ });
+ ContextMenu.Instance.addItem({ description: "Advance", event: this.snaphsotInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+ ContextMenu.Instance.addItem({ description: "Backup ", event: this.backupInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- this._timelineRef.current!.timelineContextMenu(e);
optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
- optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+ optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
// layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
optionItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
const input = document.createElement("input");
@@ -1129,9 +1226,59 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
input.click();
}
});
- ContextMenu.Instance.addItem({ description: "Options ...", subitems: optionItems, icon: "eye" });
- this._timelineRef.current!.timelineContextMenu(e);
+ optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+
+ }
+ @observable _timelineVisible = false;
+
+ intersectRect(r1: { left: number, top: number, width: number, height: number },
+ r2: { left: number, top: number, width: number, height: number }) {
+ return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+ }
+
+ @action
+ setupDragLines = () => {
+ const activeDocs = this.getActiveDocuments();
+ if (activeDocs.length > 50) {
+ DragManager.SetSnapLines([], []);
+ return;
+ }
+ const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+ const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
+ const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
+ const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => {
+ if (this.intersectRect(docDims(doc), rect)) {
+ snappableDocs.push(doc);
+ }
+ };
+ const snappableDocs: Doc[] = []; // the set of documents in the visible viewport that we will try to snap to;
+ const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
+ !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to
+ !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs
+
+ const horizLines: number[] = [];
+ const vertLines: number[] = [];
+ snappableDocs.filter(doc => !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => {
+ const { left, top, width, height } = docDims(doc);
+ const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top);
+ const docSize = this.getTransform().inverse().transformDirection(width, height);
+
+ horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
+ vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line
+ });
+ DragManager.SetSnapLines(horizLines, vertLines);
}
+ onPointerOver = (e: React.PointerEvent) => {
+ if (SnappingManager.GetIsDragging()) {
+ this.setupDragLines();
+ }
+ e.stopPropagation();
+ }
+
+ @observable private _hLines: number[] | undefined;
+ @observable private _vLines: number[] | undefined;
private childViews = () => {
const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
@@ -1165,13 +1312,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return false;
});
@computed get marqueeView() {
- return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
- addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
+ return <MarqueeView {...this.props}
+ nudge={this.nudge}
+ addDocTab={this.addDocTab}
+ activeDocuments={this.getActiveDocuments}
+ selectDocuments={this.selectDocuments}
+ addDocument={this.addDocument}
+ addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform}
+ getTransform={this.getTransform}
+ isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
</CollectionFreeFormViewPannableContents>
- <Timeline ref={this._timelineRef} {...this.props} />
+ {this._timelineVisible ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
@@ -1183,7 +1338,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const wscale = nw ? this.props.PanelWidth() / nw : 1;
return wscale < hscale ? wscale : hscale;
}
- @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
+ @computed get backgroundEvents() { return this.layoutDoc.isBackground && SnappingManager.GetIsDragging(); }
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1196,7 +1351,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
return <div className={"collectionfreeformview-container"}
ref={this.createDashEventsTarget}
- onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ onPointerOver={this.onPointerOver}
+ onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: DraggingManager.GetIsDragging() ? "all" : undefined,
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
style={{
pointerEvents: this.backgroundEvents ? "all" : undefined,
@@ -1220,7 +1376,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}}>
</div>
-
+ {// uncomment to show snap lines
+ <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
+ <svg style={{ width: "100%", height: "100%" }}>
+ {this._hLines?.map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />)}
+ {this._vLines?.map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)}
+ </svg>
+ </div>}
</div >;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 1291e7dc1..a811dd15a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -7,6 +7,7 @@
height:100%;
overflow: hidden;
border-radius: inherit;
+ user-select: none;
}
.marqueeView:focus-within {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c70301b2f..492ba6ed6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,37 +1,34 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc";
-import { InkField, InkData } from "../../../../new_fields/InkField";
-import { List } from "../../../../new_fields/List";
-import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
-import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
+import { Doc, Opt } from "../../../../fields/Doc";
+import { InkData, InkField } from "../../../../fields/InkField";
+import { List } from "../../../../fields/List";
+import { RichTextField } from "../../../../fields/RichTextField";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
import { Utils } from "../../../../Utils";
-import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { ContextMenu } from "../../ContextMenu";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { PreviewCursor } from "../../PreviewCursor";
import { SubCollectionViewProps } from "../CollectionSubView";
+import { CollectionView } from "../CollectionView";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import "./MarqueeView.scss";
import React = require("react");
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { RichTextField } from "../../../../new_fields/RichTextField";
-import { CollectionView } from "../CollectionView";
-import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { ScriptField } from "../../../../new_fields/ScriptField";
+import { InteractionUtils } from "../../../util/InteractionUtils";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
getTransform: () => Transform;
- addDocument: (doc: Doc) => boolean;
activeDocuments: () => Doc[];
selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void;
- removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
- isAnnotationOverlay?: boolean;
nudge: (x: number, y: number) => boolean;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
}
@@ -39,7 +36,7 @@ interface MarqueeViewProps {
@observer
export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps>
{
- private _mainCont = React.createRef<HTMLDivElement>();
+ @observable public static DragMarquee = false;
@observable _lastX: number = 0;
@observable _lastY: number = 0;
@observable _downX: number = 0;
@@ -108,7 +105,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
} else if (!e.ctrlKey) {
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
- const tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
+ const tbox = Docs.Create.TextDocument("", {
+ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
+ _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
+ title: "-typed text-"
+ });
const template = FormattedTextBox.DefaultLayout;
if (template instanceof Doc) {
tbox._width = NumCast(template._width);
@@ -164,12 +165,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.clientX;
this._downY = this._lastY = e.clientY;
- if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ // allow marquee if right click OR alt+left click OR space bar + left click
+ if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
+ // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
this.setPreviewCursor(e.clientX, e.clientY, true);
- if (e.altKey) {
- //e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
- e.preventDefault();
- }
+ // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
+ e.preventDefault();
+ // }
// bcz: do we need this? it kills the context menu on the main collection if !altKey
// e.stopPropagation();
}
@@ -191,14 +193,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
}
- if (e.altKey) {
+ if (e.altKey || MarqueeView.DragMarquee) {
e.preventDefault();
}
}
@action
onPointerUp = (e: PointerEvent): void => {
- if (!this.props.active(true)) this.props.selectDocuments([this.props.Document], []);
if (this._visible) {
const mselect = this.marqueeSelect();
if (!e.shiftKey) {
@@ -209,6 +210,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const docs = mselect.length ? mselect : [this.props.Document];
this.props.selectDocuments(docs, []);
}
+ const hideMarquee = () => {
+ this.hideMarquee();
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ document.removeEventListener("pointerdown", hideMarquee);
+ };
if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
MarqueeOptionsMenu.Instance.createCollection = this.collection;
MarqueeOptionsMenu.Instance.delete = this.delete;
@@ -217,17 +223,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
+ document.addEventListener("pointerdown", hideMarquee);
+ } else {
+ this.hideMarquee();
}
this.cleanupInteractions(true, this._commandExecuted);
- const hideMarquee = () => {
- this.hideMarquee();
- MarqueeOptionsMenu.Instance.fadeOut(true);
- document.removeEventListener("pointerdown", hideMarquee);
- };
- document.addEventListener("pointerdown", hideMarquee);
-
- if (e.altKey) {
+ if (e.altKey || MarqueeView.DragMarquee) {
e.preventDefault();
}
}
@@ -300,10 +302,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
delete = () => {
- this.marqueeSelect(false).map(d => this.props.removeDocument(d));
- if (this.ink) {
- // this.marqueeInkDelete(this.ink.inkData);
- }
+ this.props.removeDocument(this.marqueeSelect(false));
SelectionManager.DeselectAll();
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -349,13 +348,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
if (e instanceof KeyboardEvent ? e.key === "c" : true) {
- selected.map(d => {
- this.props.removeDocument(d);
+ selected.map(action(d => {
+ //this.props.removeDocument(d);
d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
return d;
- });
+ }));
+ this.props.removeDocument(selected);
}
const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
this.props.addDocument(newCollection);
@@ -551,7 +551,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
marqueeSelect(selectBackgrounds: boolean = true) {
const selRect = this.Bounds;
const selection: Doc[] = [];
- this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
+ this.props.activeDocuments().filter(doc => !doc.isBackground && !doc.z).map(doc => {
const layoutDoc = Doc.Layout(doc);
const x = NumCast(doc.x);
const y = NumCast(doc.y);
@@ -613,7 +613,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
render() {
return <div className="marqueeView"
- style={{ overflow: StrCast(this.props.Document.overflow), }}
+ style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
{this.props.children}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 9d09ecc3b..c0e1a0232 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,10 +1,10 @@
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from "react";
-import { Doc } from '../../../../new_fields/Doc';
-import { documentSchema } from '../../../../new_fields/documentSchemas';
-import { makeInterface } from '../../../../new_fields/Schema';
-import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../new_fields/Types';
+import { Doc } from '../../../../fields/Doc';
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { makeInterface } from '../../../../fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../fields/Types';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
@@ -13,8 +13,8 @@ import { CollectionSubView } from '../CollectionSubView';
import "./collectionMulticolumnView.scss";
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
-import { List } from '../../../../new_fields/List';
-import { returnZero } from '../../../../Utils';
+import { List } from '../../../../fields/List';
+import { returnZero, returnFalse, returnOne } from '../../../../Utils';
type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
const MulticolumnDocument = makeInterface(documentSchema);
@@ -46,12 +46,12 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout.dimUnit, "*") === DimUnit.Ratio);
+ return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio);
}
/**
- * This loops through all childLayoutPairs and extracts the values for dimUnit
- * and dimMagnitude, ignoring any that are malformed. Additionally, it then
+ * This loops through all childLayoutPairs and extracts the values for _dimUnit
+ * and _dimMagnitude, ignoring any that are malformed. Additionally, it then
* normalizes the ratio values so that one * value is always 1, with the remaining
* values proportionate to that easily readable metric.
* @returns the list of the resolved width specifiers (unit and magnitude pairs)
@@ -62,8 +62,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
this.childLayoutPairs.map(pair => {
- const unit = StrCast(pair.layout.dimUnit, "*");
- const magnitude = NumCast(pair.layout.dimMagnitude, 1);
+ const unit = StrCast(pair.layout._dimUnit, "*");
+ const magnitude = NumCast(pair.layout._dimMagnitude, 1);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
(unit === DimUnit.Ratio) && (starSum += magnitude);
widthSpecifiers.push({ magnitude, unit });
@@ -83,9 +83,9 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
setTimeout(() => {
const { ratioDefinedDocs } = this;
if (this.childLayoutPairs.length) {
- const minimum = Math.min(...ratioDefinedDocs.map(doc => NumCast(doc.dimMagnitude, 1)));
+ const minimum = Math.min(...ratioDefinedDocs.map(doc => NumCast(doc._dimMagnitude, 1)));
if (minimum !== 0) {
- ratioDefinedDocs.forEach(layout => layout.dimMagnitude = NumCast(layout.dimMagnitude, 1) / minimum, 1);
+ ratioDefinedDocs.forEach(layout => layout._dimMagnitude = NumCast(layout._dimMagnitude, 1) / minimum, 1);
}
}
});
@@ -161,8 +161,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
if (columnUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
- let width = NumCast(layout.dimMagnitude, 1);
- if (StrCast(layout.dimUnit, "*") === DimUnit.Ratio) {
+ let width = NumCast(layout._dimMagnitude, 1);
+ if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) {
width *= columnUnitLength;
}
return width;
@@ -194,8 +194,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (super.onInternalDrop(e, de)) {
de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
- d.dimUnit = "*";
- d.dimMagnitude = 1;
+ d._dimUnit = "*";
+ d._dimMagnitude = 1;
}));
}
return false;
@@ -203,6 +203,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
addDocTab = (doc: Doc, where: string) => {
@@ -215,9 +216,10 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return <ContentFittingDocumentView
Document={layout}
- DataDocument={layout.resolvedDataDoc as Doc}
+ DataDoc={layout.resolvedDataDoc as Doc}
backgroundColor={this.props.backgroundColor}
- LayoutDoc={this.props.childLayoutTemplate}
+ LayoutTemplate={this.props.ChildLayoutTemplate}
+ LayoutTemplateString={this.props.ChildLayoutString}
LibraryPath={this.props.LibraryPath}
FreezeDimensions={this.props.freezeChildDimensions}
renderDepth={this.props.renderDepth + 1}
@@ -225,21 +227,24 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
PanelHeight={height}
NativeHeight={returnZero}
NativeWidth={returnZero}
- fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ fitToBox={false}
rootSelected={this.rootSelected}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
- getTransform={dxf}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={dxf}
focus={this.props.focus}
- CollectionDoc={this.props.CollectionView?.props.Document}
- CollectionView={this.props.CollectionView}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
- active={this.props.active}
+ parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.addDocTab}
pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
/>;
}
/**
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index af0cc3b5c..602246d07 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,12 +1,12 @@
import { observer } from 'mobx-react';
-import { makeInterface } from '../../../../new_fields/Schema';
-import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { makeInterface } from '../../../../fields/Schema';
+import { documentSchema } from '../../../../fields/documentSchemas';
import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
import * as React from "react";
-import { Doc } from '../../../../new_fields/Doc';
-import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types';
+import { Doc } from '../../../../fields/Doc';
+import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../fields/Types';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
-import { Utils, returnZero } from '../../../../Utils';
+import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils';
import "./collectionMultirowView.scss";
import { computed, trace, observable, action } from 'mobx';
import { Transform } from '../../../util/Transform';
@@ -14,7 +14,7 @@ import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
import { undoBatch } from '../../../util/UndoManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
-import { List } from '../../../../new_fields/List';
+import { List } from '../../../../fields/List';
type MultirowDocument = makeInterface<[typeof documentSchema]>;
const MultirowDocument = makeInterface(documentSchema);
@@ -46,12 +46,12 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout.dimUnit, "*") === DimUnit.Ratio);
+ return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio);
}
/**
- * This loops through all childLayoutPairs and extracts the values for dimUnit
- * and dimUnit, ignoring any that are malformed. Additionally, it then
+ * This loops through all childLayoutPairs and extracts the values for _dimUnit
+ * and _dimUnit, ignoring any that are malformed. Additionally, it then
* normalizes the ratio values so that one * value is always 1, with the remaining
* values proportionate to that easily readable metric.
* @returns the list of the resolved width specifiers (unit and magnitude pairs)
@@ -62,8 +62,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
let starSum = 0;
const heightSpecifiers: HeightSpecifier[] = [];
this.childLayoutPairs.map(pair => {
- const unit = StrCast(pair.layout.dimUnit, "*");
- const magnitude = NumCast(pair.layout.dimMagnitude, 1);
+ const unit = StrCast(pair.layout._dimUnit, "*");
+ const magnitude = NumCast(pair.layout._dimMagnitude, 1);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
(unit === DimUnit.Ratio) && (starSum += magnitude);
heightSpecifiers.push({ magnitude, unit });
@@ -83,9 +83,9 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
setTimeout(() => {
const { ratioDefinedDocs } = this;
if (this.childLayoutPairs.length) {
- const minimum = Math.min(...ratioDefinedDocs.map(layout => NumCast(layout.dimMagnitude, 1)));
+ const minimum = Math.min(...ratioDefinedDocs.map(layout => NumCast(layout._dimMagnitude, 1)));
if (minimum !== 0) {
- ratioDefinedDocs.forEach(layout => layout.dimMagnitude = NumCast(layout.dimMagnitude, 1) / minimum);
+ ratioDefinedDocs.forEach(layout => layout._dimMagnitude = NumCast(layout._dimMagnitude, 1) / minimum);
}
}
});
@@ -161,8 +161,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
if (rowUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
- let height = NumCast(layout.dimMagnitude, 1);
- if (StrCast(layout.dimUnit, "*") === DimUnit.Ratio) {
+ let height = NumCast(layout._dimMagnitude, 1);
+ if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) {
height *= rowUnitLength;
}
return height;
@@ -194,8 +194,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (super.onInternalDrop(e, de)) {
de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
- d.dimUnit = "*";
- d.dimMagnitude = 1;
+ d._dimUnit = "*";
+ d._dimMagnitude = 1;
}));
}
return false;
@@ -203,7 +203,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
-
+ @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
@@ -215,9 +215,10 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return <ContentFittingDocumentView
Document={layout}
- DataDocument={layout.resolvedDataDoc as Doc}
+ DataDoc={layout.resolvedDataDoc as Doc}
backgroundColor={this.props.backgroundColor}
- LayoutDoc={this.props.childLayoutTemplate}
+ LayoutTemplate={this.props.ChildLayoutTemplate}
+ LayoutTemplateString={this.props.ChildLayoutString}
LibraryPath={this.props.LibraryPath}
FreezeDimensions={this.props.freezeChildDimensions}
renderDepth={this.props.renderDepth + 1}
@@ -225,21 +226,24 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
PanelHeight={height}
NativeHeight={returnZero}
NativeWidth={returnZero}
- fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ fitToBox={false}
rootSelected={this.rootSelected}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
- getTransform={dxf}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={dxf}
focus={this.props.focus}
- CollectionDoc={this.props.CollectionView?.props.Document}
- CollectionView={this.props.CollectionView}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
- active={this.props.active}
+ parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.addDocTab}
pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
/>;
}
/**
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index e1e604686..734915a93 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,8 +1,8 @@
import * as React from "react";
import { observer } from "mobx-react";
import { observable, action } from "mobx";
-import { Doc } from "../../../../new_fields/Doc";
-import { NumCast, StrCast } from "../../../../new_fields/Types";
+import { Doc } from "../../../../fields/Doc";
+import { NumCast, StrCast } from "../../../../fields/Types";
import { DimUnit } from "./CollectionMulticolumnView";
import { UndoManager } from "../../../util/UndoManager";
@@ -43,12 +43,12 @@ export default class ResizeBar extends React.Component<ResizerProps> {
const unitLength = columnUnitLength();
if (unitLength) {
if (toNarrow) {
- const scale = StrCast(toNarrow.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
- toNarrow.dimMagnitude = Math.max(0.05, NumCast(toNarrow.dimMagnitude, 1) - Math.abs(movementX) / scale);
+ const scale = StrCast(toNarrow._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toNarrow._dimMagnitude = Math.max(0.05, NumCast(toNarrow._dimMagnitude, 1) - Math.abs(movementX) / scale);
}
if (toWiden) {
- const scale = StrCast(toWiden.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
- toWiden.dimMagnitude = Math.max(0.05, NumCast(toWiden.dimMagnitude, 1) + Math.abs(movementX) / scale);
+ const scale = StrCast(toWiden._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toWiden._dimMagnitude = Math.max(0.05, NumCast(toWiden._dimMagnitude, 1) + Math.abs(movementX) / scale);
}
}
}
@@ -56,17 +56,17 @@ export default class ResizeBar extends React.Component<ResizerProps> {
private get isActivated() {
const { toLeft, toRight } = this.props;
if (toLeft && toRight) {
- if (StrCast(toLeft.dimUnit, "*") === DimUnit.Pixel && StrCast(toRight.dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toLeft._dimUnit, "*") === DimUnit.Pixel && StrCast(toRight._dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
} else if (toLeft) {
- if (StrCast(toLeft.dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toLeft._dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
} else if (toRight) {
- if (StrCast(toRight.dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toRight._dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index 5b2054428..9985a9fba 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -1,8 +1,8 @@
import * as React from "react";
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Doc } from "../../../../new_fields/Doc";
-import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types";
+import { Doc } from "../../../../fields/Doc";
+import { NumCast, StrCast, BoolCast } from "../../../../fields/Types";
import { EditableView } from "../../EditableView";
import { DimUnit } from "./CollectionMulticolumnView";
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index 899577fd5..aa5439fa4 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,8 +1,8 @@
import * as React from "react";
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Doc } from "../../../../new_fields/Doc";
-import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types";
+import { Doc } from "../../../../fields/Doc";
+import { NumCast, StrCast, BoolCast } from "../../../../fields/Types";
import { EditableView } from "../../EditableView";
import { DimUnit } from "./CollectionMultirowView";
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 9df8cc3e2..d0bc4d01c 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,8 +1,8 @@
import * as React from "react";
import { observer } from "mobx-react";
import { observable, action } from "mobx";
-import { Doc } from "../../../../new_fields/Doc";
-import { NumCast, StrCast } from "../../../../new_fields/Types";
+import { Doc } from "../../../../fields/Doc";
+import { NumCast, StrCast } from "../../../../fields/Types";
import { DimUnit } from "./CollectionMultirowView";
import { UndoManager } from "../../../util/UndoManager";
@@ -41,12 +41,12 @@ export default class ResizeBar extends React.Component<ResizerProps> {
const unitLength = columnUnitLength();
if (unitLength) {
if (toNarrow) {
- const scale = StrCast(toNarrow.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
- toNarrow.dimMagnitude = Math.max(0.05, NumCast(toNarrow.dimMagnitude, 1) - Math.abs(movementY) / scale);
+ const scale = StrCast(toNarrow._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toNarrow._dimMagnitude = Math.max(0.05, NumCast(toNarrow._dimMagnitude, 1) - Math.abs(movementY) / scale);
}
if (toWiden) {
- const scale = StrCast(toWiden.dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
- toWiden.dimMagnitude = Math.max(0.05, NumCast(toWiden.dimMagnitude, 1) + Math.abs(movementY) / scale);
+ const scale = StrCast(toWiden._dimUnit, "*") === DimUnit.Ratio ? unitLength : 1;
+ toWiden._dimMagnitude = Math.max(0.05, NumCast(toWiden._dimMagnitude, 1) + Math.abs(movementY) / scale);
}
}
}
@@ -54,17 +54,17 @@ export default class ResizeBar extends React.Component<ResizerProps> {
private get isActivated() {
const { toTop, toBottom } = this.props;
if (toTop && toBottom) {
- if (StrCast(toTop.dimUnit, "*") === DimUnit.Pixel && StrCast(toBottom.dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toTop._dimUnit, "*") === DimUnit.Pixel && StrCast(toBottom._dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
} else if (toTop) {
- if (StrCast(toTop.dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toTop._dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
} else if (toBottom) {
- if (StrCast(toBottom.dimUnit, "*") === DimUnit.Pixel) {
+ if (StrCast(toBottom._dimUnit, "*") === DimUnit.Pixel) {
return false;
}
return true;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index b7f3dd995..5fb4cf3c6 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -3,8 +3,8 @@ import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTime
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
-import { StrCast } from "../../../new_fields/Types";
+import { Doc } from "../../../fields/Doc";
+import { StrCast } from "../../../fields/Types";
import { Utils } from "../../../Utils";
import { LinkManager } from "../../util/LinkManager";
import './LinkEditor.scss';
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index b768eacc3..786d6be47 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -4,7 +4,7 @@ import { DocumentView } from "../nodes/DocumentView";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
-import { Doc } from "../../../new_fields/Doc";
+import { Doc } from "../../../fields/Doc";
import { LinkManager } from "../../util/LinkManager";
import { LinkMenuGroup } from "./LinkMenuGroup";
import { faTrash } from '@fortawesome/free-solid-svg-icons';
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 928413a11..89deb3a55 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,15 +1,15 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { Doc } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { Docs } from "../../documents/Documents";
import { DragManager, SetupDrag } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
import { DocumentView } from "../nodes/DocumentView";
import './LinkMenu.scss';
-import { LinkMenuItem } from "./LinkMenuItem";
+import { LinkMenuItem, StartLinkTargetsDrag } from "./LinkMenuItem";
import React = require("react");
interface LinkMenuGroupProps {
@@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
document.removeEventListener("pointerup", this.onLinkButtonUp);
const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
- DragManager.StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets);
+ StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets);
}
e.stopPropagation();
}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d091e06ef..17cd33241 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -3,8 +3,8 @@ import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { Doc } from '../../../new_fields/Doc';
-import { Cast, StrCast } from '../../../new_fields/Types';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Cast, StrCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
import { ContextMenu } from '../ContextMenu';
@@ -26,6 +26,41 @@ interface LinkMenuItemProps {
addDocTab: (document: Doc, where: string) => boolean;
}
+// drag links and drop link targets (aliasing them if needed)
+export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
+ const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+
+ if (draggedDocs.length) {
+ const moddrag: Doc[] = [];
+ for (const draggedDoc of draggedDocs) {
+ const doc = await Cast(draggedDoc.annotationOn, Doc);
+ if (doc) moddrag.push(doc);
+ }
+
+ const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+ dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+ docView.props.removeDocument?.(doc);
+ addDocument(doc);
+ return true;
+ };
+ const containingView = docView.props.ContainingCollectionView;
+ const finishDrag = (e: DragManager.DragCompleteEvent) =>
+ e.docDragData && (e.docDragData.droppedDocuments =
+ dragData.draggedDocuments.reduce((droppedDocs, d) => {
+ const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
+ if (dvs.length) {
+ dvs.forEach(dv => droppedDocs.push(dv.props.Document));
+ } else {
+ droppedDocs.push(Doc.MakeAlias(d));
+ }
+ return droppedDocs;
+ }, [] as Doc[]));
+
+ DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
+ }
+}
+
+
@observer
export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
@@ -83,7 +118,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
document.removeEventListener("pointerup", this.onLinkButtonUp);
this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
- DragManager.StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
+ StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
}
e.stopPropagation();
}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6ff6d1b42..1a935d9b0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -2,25 +2,25 @@ import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
import { observer } from "mobx-react";
import "./AudioBox.scss";
-import { Cast, DateCast, NumCast } from "../../../new_fields/Types";
-import { AudioField, nullAudio } from "../../../new_fields/URLField";
+import { Cast, DateCast, NumCast } from "../../../fields/Types";
+import { AudioField, nullAudio } from "../../../fields/URLField";
import { ViewBoxBaseComponent } from "../DocComponent";
-import { makeInterface, createSchema } from "../../../new_fields/Schema";
-import { documentSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface, createSchema } from "../../../fields/Schema";
+import { documentSchema } from "../../../fields/documentSchemas";
import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
-import { DateField } from "../../../new_fields/DateField";
+import { DateField } from "../../../fields/DateField";
import { SelectionManager } from "../../util/SelectionManager";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../fields/Doc";
import { ContextMenuProps } from "../ContextMenuItem";
import { ContextMenu } from "../ContextMenu";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { Id } from "../../../fields/FieldSymbols";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentView } from "./DocumentView";
-import { Docs } from "../../documents/Documents";
-import { ComputedField } from "../../../new_fields/ScriptField";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ComputedField } from "../../../fields/ScriptField";
import { Networking } from "../../Network";
-import { Upload } from "../../../server/SharedMediaTypes";
+import { LinkAnchorBox } from "./LinkAnchorBox";
// testing testing
@@ -56,7 +56,6 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
@computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
set audioState(value) { this.dataDoc.audioState = value; }
public static SetScrubTime = (timeInMillisFrom1970: number) => { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); };
- public static ActiveRecordings: Doc[] = [];
@computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); }
@@ -144,7 +143,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date());
- AudioBox.ActiveRecordings.push(this.props.Document);
+ DocUtils.ActiveRecordings.push(this.props.Document);
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
@@ -171,8 +170,8 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000;
this.audioState = "paused";
this._stream?.getAudioTracks()[0].stop();
- const ind = AudioBox.ActiveRecordings.indexOf(this.props.Document);
- ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1));
+ const ind = DocUtils.ActiveRecordings.indexOf(this.props.Document);
+ ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
});
recordClick = (e: React.MouseEvent) => {
@@ -266,7 +265,8 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
NativeHeight={returnZero}
NativeWidth={returnZero}
rootSelected={returnFalse}
- layoutKey={Doc.LinkEndpoint(l, la2)}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
ContainingCollectionDoc={this.props.Document}
parentActive={returnTrue}
bringToFront={emptyFunction}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 1c7d116c5..5d6e587d9 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,26 +1,21 @@
import { computed, IReactionDisposer, observable, reaction, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { Transform } from "../../util/Transform";
import { DocComponent } from "../DocComponent";
import "./CollectionFreeFormDocumentView.scss";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import React = require("react");
-import { PositionDocument } from "../../../new_fields/documentSchemas";
-import { TraceMobx } from "../../../new_fields/util";
+import { Document } from "../../../fields/documentSchemas";
+import { TraceMobx } from "../../../fields/util";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, z: number, transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
- x?: number;
- y?: number;
- z?: number;
zIndex?: number;
highlight?: boolean;
- width?: number;
- height?: number;
jitterRotation: number;
transition?: string;
fitToBox?: boolean;
@@ -28,7 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
}
@observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, Document>(Document) {
@observable _animPos: number[] | undefined = undefined;
random(min: number, max: number) { // min should not be equal to max
const mseed = Math.abs(this.X * this.Y);
@@ -38,13 +33,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
- get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
- get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
+ get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
+ get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
get Highlight() { return this.dataProvider?.highlight; }
- get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
+ get width() { return this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
get height() {
- const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.height : this.layoutDoc[HeightSym]();
+ const hgt = this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.height : this.layoutDoc[HeightSym]();
return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
}
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
@@ -75,10 +70,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
- getTransform = (): Transform => this.props.ScreenToLocalTransform()
- .translate(-this.X, -this.Y)
- .scale(1 / this.contentScaling())
-
+ getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y).scale(1 / this.contentScaling());
focusDoc = (doc: Doc) => this.props.focus(doc, false);
NativeWidth = () => this.nativeWidth;
NativeHeight = () => this.nativeHeight;
@@ -115,13 +107,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight} />
: <ContentFittingDocumentView {...this.props}
- CollectionDoc={this.props.ContainingCollectionDoc}
- DataDocument={this.props.DataDoc}
- getTransform={this.getTransform}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ DataDoc={this.props.DataDoc}
+ ScreenToLocalTransform={this.getTransform}
NativeHeight={this.NativeHeight}
NativeWidth={this.NativeWidth}
- active={this.props.parentActive}
- focus={this.focusDoc}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
/>}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 6e4341b27..6d53915ea 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -1,10 +1,10 @@
import React = require("react");
import { observer } from "mobx-react";
import { SketchPicker } from 'react-color';
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface } from "../../../new_fields/Schema";
-import { StrCast } from "../../../new_fields/Types";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import { StrCast } from "../../../fields/Types";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { SelectionManager } from "../../util/SelectionManager";
import { ViewBoxBaseComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
@@ -24,9 +24,13 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
- <SketchPicker onChange={InkingControl.Instance.switchColor}
+ <SketchPicker onChange={InkingControl.Instance.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
color={StrCast(CurrentUserUtils.ActivePen ? CurrentUserUtils.ActivePen.backgroundColor : undefined,
StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} />
+ <div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
+ <div>{InkingControl.Instance.selectedWidth ?? 2}</div>
+ <input type="range" value={InkingControl.Instance.selectedWidth ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchWidth(e.target.value)} />
+ </div>
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 641797cac..a90b4668e 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -2,53 +2,23 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import "react-table/react-table.css";
-import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast } from "../../../new_fields/Types";
-import { TraceMobx } from "../../../new_fields/util";
+import { Doc, Opt, WidthSym, HeightSym } from "../../../fields/Doc";
+import { NumCast, StrCast, Cast } from "../../../fields/Types";
+import { TraceMobx } from "../../../fields/util";
import { emptyFunction, returnOne } from "../../../Utils";
-import { Transform } from "../../util/Transform";
-import { CollectionView } from "../collections/CollectionView";
import '../DocumentDecorations.scss';
-import { DocumentView } from "../nodes/DocumentView";
+import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import "./ContentFittingDocumentView.scss";
-import { dropActionType } from "../../util/DragManager";
-interface ContentFittingDocumentViewProps {
- Document: Doc;
- DataDocument?: Doc;
- LayoutDoc?: () => Opt<Doc>;
- NativeWidth?: () => number;
- NativeHeight?: () => number;
- FreezeDimensions?: boolean;
- LibraryPath: Doc[];
- renderDepth: number;
- fitToBox?: boolean;
- layoutKey?: string;
- dropAction?: dropActionType;
- PanelWidth: () => number;
- PanelHeight: () => number;
- focus?: (doc: Doc) => void;
- CollectionView?: CollectionView;
- CollectionDoc?: Doc;
- onClick?: ScriptField;
- backgroundColor?: (doc: Doc) => string | undefined;
- getTransform: () => Transform;
- addDocument?: (document: Doc) => boolean;
- moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
- removeDocument?: (document: Doc) => boolean;
- active: (outsideReaction: boolean) => boolean;
- whenActiveChanged: (isActive: boolean) => void;
- addDocTab: (document: Doc, where: string) => boolean;
- pinToPres: (document: Doc) => void;
- dontRegisterView?: boolean;
- rootSelected: (outsideReaction?: boolean) => boolean;
-}
@observer
-export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
+export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
- private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
+ private get layoutDoc() {
+ return this.props.LayoutTemplate?.() ||
+ (this.props.layoutKey && Doc.Layout(this.props.Document, Cast(this.props.Document[this.props.layoutKey], Doc, null))) ||
+ Doc.Layout(this.props.Document);
+ }
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
@@ -67,7 +37,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
@computed get panelWidth() { return this.nativeWidth && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
@computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
- private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
+ private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
@@ -89,8 +59,9 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
}}>
<DocumentView {...this.props}
Document={this.props.Document}
- DataDoc={this.props.DataDocument}
- LayoutDoc={this.props.LayoutDoc}
+ DataDoc={this.props.DataDoc}
+ LayoutTemplate={this.props.LayoutTemplate}
+ LayoutTemplateString={this.props.LayoutTemplateString}
LibraryPath={this.props.LibraryPath}
NativeWidth={this.nativeWidth}
NativeHeight={this.nativeHeight}
@@ -106,11 +77,11 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
removeDocument={this.props.removeDocument}
moveDocument={this.props.moveDocument}
whenActiveChanged={this.props.whenActiveChanged}
- ContainingCollectionView={this.props.CollectionView}
- ContainingCollectionDoc={this.props.CollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
- parentActive={this.props.active}
+ parentActive={this.props.parentActive}
ScreenToLocalTransform={this.getTransform}
renderDepth={this.props.renderDepth}
focus={this.props.focus || emptyFunction}
diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocHolderBox.scss
index ce21391ce..6a9ef0b6f 100644
--- a/src/client/views/nodes/DocumentBox.scss
+++ b/src/client/views/nodes/DocHolderBox.scss
@@ -2,11 +2,12 @@
width: 100%;
height: 100%;
pointer-events: all;
- background: gray;
+ position: absolute;
.documentBox-lock {
margin: auto;
color: white;
position: absolute;
+ padding: 3px;
}
.contentFittingDocumentView {
position: absolute;
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
new file mode 100644
index 000000000..0c5239d66
--- /dev/null
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -0,0 +1,213 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, IReactionDisposer, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Field } from "../../../fields/Doc";
+import { collectionSchema, documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface, listSpec } from "../../../fields/Schema";
+import { ComputedField } from "../../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { TraceMobx } from "../../../fields/util";
+import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { DragManager } from "../../util/DragManager";
+import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import "./DocHolderBox.scss";
+import { DocumentView } from "./DocumentView";
+import { FieldView, FieldViewProps } from "./FieldView";
+import React = require("react");
+
+type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
+
+@observer
+export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
+ _prevSelectionDisposer: IReactionDisposer | undefined;
+ _dropDisposer?: DragManager.DragDropDisposer;
+ _selections: Doc[] = [];
+ _contRef = React.createRef<HTMLDivElement>();
+ _curSelection = -1;
+ componentDidMount() {
+ this._prevSelectionDisposer = reaction(() => this.dataDoc[this.fieldKey], (data) => {
+ if (data instanceof Doc && !this.isSelectionLocked()) {
+ this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1);
+ this._selections.push(data);
+ this._curSelection = this._selections.length - 1;
+ }
+ });
+ }
+ componentWillUnmount() {
+ this._prevSelectionDisposer?.();
+ }
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
+ funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" });
+ funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" });
+ funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "<KeyValueBox {...props} />", icon: "project-diagram" });
+
+ ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ }
+ lockSelection = () => {
+ this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey];
+ }
+ showSelection = () => {
+ this.dataDoc[this.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`);
+ }
+ isSelectionLocked = () => {
+ const kvpstring = Field.toKeyValueString(this.dataDoc, this.fieldKey);
+ return !kvpstring || kvpstring.includes("DOC");
+ }
+ toggleLockSelection = () => {
+ !this.isSelectionLocked() ? this.lockSelection() : this.showSelection();
+ return true;
+ }
+ prevSelection = () => {
+ this.lockSelection();
+ if (this._curSelection > 0) {
+ this.dataDoc[this.fieldKey] = this._selections[--this._curSelection];
+ return true;
+ }
+ }
+ nextSelection = () => {
+ if (this._curSelection < this._selections.length - 1 && this._selections.length) {
+ this.dataDoc[this.fieldKey] = this._selections[++this._curSelection];
+ return true;
+ }
+ }
+ onPointerDown = (e: React.PointerEvent) => {
+ if (this.active() && e.button === 0 && !e.ctrlKey) {
+ e.stopPropagation();
+ }
+ }
+ onLockClick = (e: React.MouseEvent) => {
+ this.toggleLockSelection();
+ (e.nativeEvent as any).formattedHandled = true;
+ e.stopPropagation();
+ }
+ get xPad() { return NumCast(this.rootDoc._xPadding); }
+ get yPad() { return NumCast(this.rootDoc._yPadding); }
+ onClick = (e: React.MouseEvent) => {
+ let hitWidget: boolean | undefined = false;
+ if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
+ else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
+ else {
+ if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection();
+ if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection();
+ }
+ if (hitWidget) {
+ (e.nativeEvent as any).formattedHandled = true;
+ e.stopPropagation();
+ }
+ }
+ pwidth = () => this.props.PanelWidth() - 2 * this.xPad;
+ pheight = () => this.props.PanelHeight() - 2 * this.yPad;
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad);
+ isActive = (outsideReaction: boolean) => this.active(outsideReaction) || this.props.renderDepth <= 1;
+ layoutTemplateDoc = () => Cast(this.layoutDoc.childLayoutTemplate, Doc, null);
+ get renderContents() {
+ const containedDoc = Cast(this.dataDoc[this.fieldKey], Doc, null);
+ const layoutTemplate = StrCast(this.layoutDoc.childLayoutString);
+ const contents = !(containedDoc instanceof Doc) ||
+ Cast(containedDoc[Doc.LayoutFieldKey(containedDoc)], listSpec(Doc), null)?.includes(this.rootDoc)
+ ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ?
+ <DocumentView
+ Document={containedDoc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
+ ContainingCollectionDoc={undefined}
+ fitToBox={true}
+ backgroundColor={this.props.backgroundColor}
+ LayoutTemplateString={layoutTemplate}
+ LayoutTemplate={this.layoutTemplateDoc}
+ rootSelected={this.props.isSelected}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ ScreenToLocalTransform={this.getTransform}
+ renderDepth={containedDoc.type !== DocumentType.DOCHOLDER && !this.props.renderDepth ? 0 : this.props.renderDepth + 1}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ PanelWidth={this.pwidth}
+ PanelHeight={this.pheight}
+ focus={this.props.focus}
+ parentActive={this.isActive}
+ dontRegisterView={true}
+ whenActiveChanged={this.props.whenActiveChanged}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne} /> :
+ <ContentFittingDocumentView
+ Document={containedDoc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
+ ContainingCollectionDoc={undefined}
+ fitToBox={true}
+ backgroundColor={this.props.backgroundColor}
+ ignoreAutoHeight={true}
+ LayoutTemplateString={layoutTemplate}
+ LayoutTemplate={this.layoutTemplateDoc}
+ rootSelected={this.props.isSelected}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ ScreenToLocalTransform={this.getTransform}
+ renderDepth={containedDoc.type !== DocumentType.DOCHOLDER && !this.props.renderDepth ? 0 : this.props.renderDepth + 1}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ PanelWidth={this.pwidth}
+ PanelHeight={this.pheight}
+ focus={this.props.focus}
+ parentActive={this.isActive}
+ dontRegisterView={true}
+ whenActiveChanged={this.props.whenActiveChanged}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
+ />;
+ return contents;
+ }
+ render() {
+ const containedDoc = Cast(this.dataDoc[this.fieldKey], Doc, null);
+ TraceMobx();
+ return <div className="documentBox-container" ref={this._contRef}
+ onContextMenu={this.specificContextMenu}
+ onPointerDown={this.onPointerDown} onClick={this.onClick}
+ style={{
+ background: this.props.backgroundColor?.(containedDoc),
+ border: `#00000021 solid ${this.xPad}px`,
+ borderTop: `#0000005e solid ${this.yPad}px`,
+ borderBottom: `#0000005e solid ${this.yPad}px`,
+ }}>
+ {this.renderContents}
+ <div className="documentBox-lock" onClick={this.onLockClick} ref={this.createDropTarget}
+ style={{ marginTop: - this.yPad, background: "black" }}>
+ <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" />
+ </div>
+ </div >;
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.complete.docDragData) {
+ if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) {
+ const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null);
+ this.layoutDoc.childLayoutTemplate = doc;
+ }
+ }
+ }
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ this._dropDisposer?.();
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.rootDoc));
+ }
+
+}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
deleted file mode 100644
index d4d997120..000000000
--- a/src/client/views/nodes/DocumentBox.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IReactionDisposer, reaction, computed } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface } from "../../../new_fields/Schema";
-import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyPath } from "../../../Utils";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
-import "./DocumentBox.scss";
-import { FieldView, FieldViewProps } from "./FieldView";
-import React = require("react");
-import { TraceMobx } from "../../../new_fields/util";
-import { Docs } from "../../documents/Documents";
-
-type DocHolderBoxSchema = makeInterface<[typeof documentSchema]>;
-const DocHolderBoxDocument = makeInterface(documentSchema);
-
-@observer
-export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
- _prevSelectionDisposer: IReactionDisposer | undefined;
- _selections: Doc[] = [];
- _curSelection = -1;
- componentDidMount() {
- this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => {
- if (data instanceof Doc && !this.isSelectionLocked()) {
- this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1);
- this._selections.push(data);
- this._curSelection = this._selections.length - 1;
- }
- });
- }
- componentWillUnmount() {
- this._prevSelectionDisposer?.();
- }
- specificContextMenu = (e: React.MouseEvent): void => {
- const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
- funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" });
- funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" });
- funcs.push({ description: `Show ${this.layoutDoc.childTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childTemplateName = this.layoutDoc.childTemplateName ? undefined : "keyValue", icon: "project-diagram" });
-
- ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
- }
- lockSelection = () => {
- this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey];
- }
- showSelection = () => {
- this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`);
- }
- isSelectionLocked = () => {
- const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey);
- return !kvpstring || kvpstring.includes("DOC");
- }
- toggleLockSelection = () => {
- !this.isSelectionLocked() ? this.lockSelection() : this.showSelection();
- return true;
- }
- prevSelection = () => {
- this.lockSelection();
- if (this._curSelection > 0) {
- this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection];
- return true;
- }
- }
- nextSelection = () => {
- if (this._curSelection < this._selections.length - 1 && this._selections.length) {
- this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection];
- return true;
- }
- }
- onPointerDown = (e: React.PointerEvent) => {
- if (this.active() && e.button === 0 && !e.ctrlKey) {
- e.stopPropagation();
- }
- }
- onLockClick = (e: React.MouseEvent) => {
- this.toggleLockSelection();
- (e.nativeEvent as any).formattedHandled = true;
- e.stopPropagation();
- }
- get xPad() { return NumCast(this.props.Document._xPadding); }
- get yPad() { return NumCast(this.props.Document._yPadding); }
- onClick = (e: React.MouseEvent) => {
- let hitWidget: boolean | undefined = false;
- if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
- else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })();
- else {
- if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection();
- if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection();
- }
- if (hitWidget) {
- (e.nativeEvent as any).formattedHandled = true;
- e.stopPropagation();
- }
- }
- _contRef = React.createRef<HTMLDivElement>();
- pwidth = () => this.props.PanelWidth() - 2 * this.xPad;
- pheight = () => this.props.PanelHeight() - 2 * this.yPad;
- getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad);
- get renderContents() {
- const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null);
- const childTemplateName = StrCast(this.layoutDoc.childTemplateName);
- if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) {
- setTimeout(() => {
- Doc.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
- Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
- }, 0);
- }
- const contents = !(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView
- Document={containedDoc}
- DataDocument={undefined}
- LibraryPath={emptyPath}
- CollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
- fitToBox={true}
- layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
- rootSelected={this.props.isSelected}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- getTransform={this.getTransform}
- renderDepth={this.props.renderDepth + 1}
- PanelWidth={this.pwidth}
- PanelHeight={this.pheight}
- focus={this.props.focus}
- active={this.props.active}
- dontRegisterView={!this.isSelectionLocked()}
- whenActiveChanged={this.props.whenActiveChanged}
- />;
- return contents;
- }
- render() {
- TraceMobx();
- return <div className="documentBox-container" ref={this._contRef}
- onContextMenu={this.specificContextMenu}
- onPointerDown={this.onPointerDown} onClick={this.onClick}
- style={{
- background: StrCast(this.layoutDoc.backgroundColor),
- border: `#00000021 solid ${this.xPad}px`,
- borderTop: `#0000005e solid ${this.yPad}px`,
- borderBottom: `#0000005e solid ${this.yPad}px`,
- }}>
- {this.renderContents}
- <div className="documentBox-lock" onClick={this.onLockClick}
- style={{ marginTop: - this.yPad }}>
- <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" />
- </div>
- </div >;
- }
-}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4d20d3e2c..f4785bb0c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,7 +1,7 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, Field } from "../../../new_fields/Doc";
-import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { Doc, Opt, Field } from "../../../fields/Doc";
+import { Cast, StrCast, NumCast } from "../../../fields/Types";
import { OmitKeys, Without, emptyPath } from "../../../Utils";
import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -14,7 +14,7 @@ import { LabelBox } from "./LabelBox";
import { SliderBox } from "./SliderBox";
import { LinkBox } from "./LinkBox";
import { ScriptingBox } from "./ScriptingBox";
-import { DocHolderBox } from "./DocumentBox";
+import { DocHolderBox } from "./DocHolderBox";
import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FontIconBox } from "./FontIconBox";
@@ -35,8 +35,8 @@ import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
import React = require("react");
import { RecommendationsBox } from "../RecommendationsBox";
-import { TraceMobx } from "../../../new_fields/util";
-import { ScriptField } from "../../../new_fields/ScriptField";
+import { TraceMobx } from "../../../fields/util";
+import { ScriptField } from "../../../fields/ScriptField";
import XRegExp = require("xregexp");
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -77,11 +77,11 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
render() {
const style: { [key: string]: any } = {};
const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "Document", "key", "onInput", "onClick", "__proto__"]).omit;
+ const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.props.RootDoc, this: this.props.Document }).result as string || "";
+ };
Object.keys(divKeys).map((prop: string) => {
const p = (this.props as any)[prop] as string;
- const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.props.RootDoc, this: this.props.Document }).result as string || "";
- };
style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
});
const Tag = this.props.htmltag as keyof JSX.IntrinsicElements;
@@ -96,8 +96,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
isSelected: (outsideReaction: boolean) => boolean,
select: (ctrl: boolean) => void,
layoutKey: string,
- forceLayout?: string,
- forceFieldKey?: string,
hideOnLeave?: boolean,
makeLink?: () => Opt<Doc>, // function to call when a link is made
}> {
@@ -105,6 +103,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
TraceMobx();
if (!this.layoutDoc) return "<p>awaiting layout</p>";
// const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string"); // bcz: replaced this with below... is it right?
+ if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string");
if (this.props.layoutKey === "layout_keyValue") {
return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
@@ -127,8 +126,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
get layoutDoc() {
const params = StrCast(this.props.Document.PARAMS);
// bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script
- // const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
- const template: Doc = this.props.LayoutDoc?.() ||
+ // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
+ const template: Doc = this.props.LayoutTemplate?.() || (this.props.LayoutTemplateString && this.props.Document) ||
(this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
@@ -136,7 +135,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
const list = {
- ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit,
+ ...OmitKeys(this.props, ['parentActive'], "", (obj: any) => obj.active = this.props.parentActive).omit,
RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc,
Document: this.layoutDoc,
DataDoc: this.dataDoc,
@@ -186,25 +185,22 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
// layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
- this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
- <FormattedTextBox {...bindings.props} fieldKey={this.props.forceFieldKey} />
- :
- <ObserverJsxParser
- key={42}
- blacklistedAttrs={[]}
- renderInWrapper={false}
- components={{
- FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
- CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
- ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
- RecommendationsBox, ScreenshotBox, HTMLtag
- }}
- bindings={bindings}
- jsx={layoutFrame}
- showWarnings={true}
-
- onError={(test: any) => { console.log(test); }}
- />;
+ <ObserverJsxParser
+ key={42}
+ blacklistedAttrs={[]}
+ renderInWrapper={false}
+ components={{
+ FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
+ CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
+ PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
+ ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
+ RecommendationsBox, ScreenshotBox, HTMLtag
+ }}
+ bindings={bindings}
+ jsx={layoutFrame}
+ showWarnings={true}
+
+ onError={(test: any) => { console.log(test); }}
+ />;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index f56f5e829..fb54f18e8 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -3,7 +3,7 @@ import * as React from "react";
import { DocumentView } from "./DocumentView";
import { DocumentManager } from "../../util/DocumentManager";
import { Transformer, Scripting, ts } from "../../util/Scripting";
-import { Field } from "../../../new_fields/Doc";
+import { Field } from "../../../fields/Doc";
@observer
export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fdcaa2df3..340fa06a8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,26 +4,26 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { InkTool } from '../../../new_fields/InkField';
-import { RichTextField } from '../../../new_fields/RichTextField';
-import { listSpec } from "../../../new_fields/Schema";
-import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField';
-import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField';
-import { TraceMobx } from '../../../new_fields/util';
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../fields/Doc";
+import { Document } from '../../../fields/documentSchemas';
+import { Id } from '../../../fields/FieldSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { listSpec } from "../../../fields/Schema";
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { ScriptField } from '../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
+import { ImageField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { ClientRecommender } from '../../ClientRecommender';
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { ClientUtils } from '../../util/ClientUtils';
import { DocumentManager } from "../../util/DocumentManager";
+import { SnappingManager } from '../../util/SnappingManager';
import { DragManager, dropActionType } from "../../util/DragManager";
import { InteractionUtils } from '../../util/InteractionUtils';
import { Scripting } from '../../util/Scripting';
@@ -42,6 +42,7 @@ import { InkingControl } from '../InkingControl';
import { KeyphraseQueryView } from '../KeyphraseQueryView';
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
+import { LinkAnchorBox } from './LinkAnchorBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
@@ -58,21 +59,26 @@ export interface DocumentViewProps {
NativeHeight: () => number;
Document: Doc;
DataDoc?: Doc;
- LayoutDoc?: () => Opt<Doc>;
+ LayoutTemplateString?: string;
+ LayoutTemplate?: () => Opt<Doc>;
LibraryPath: Doc[];
fitToBox?: boolean;
+ ignoreAutoHeight?: boolean;
contextMenuItems?: () => { script: ScriptField, label: string }[];
rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
onClick?: ScriptField;
+ onDoubleClick?: ScriptField;
onPointerDown?: ScriptField;
onPointerUp?: ScriptField;
+ treeViewId?: string;
dropAction?: dropActionType;
dragDivName?: string;
nudge?: (x: number, y: number) => void;
- addDocument?: (doc: Doc) => boolean;
- removeDocument?: (doc: Doc) => boolean;
- moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+ addDocument?: (doc: Doc | Doc[]) => boolean;
+ removeDocument?: (doc: Doc | Doc[]) => boolean;
+ moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
+ setupDragLines?: () => void;
renderDepth: number;
ContentScaling: () => number;
PanelWidth: () => number;
@@ -116,6 +122,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
@computed get onClickHandler() { return this.props.onClick || Cast(this.layoutDoc.onClick, ScriptField, null) || this.Document.onClick; }
+ @computed get onDoubleClickHandler() { return this.props.onDoubleClick || Cast(this.layoutDoc.onDoubleClick, ScriptField, null) || this.Document.onDoubleClick; }
@computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
@computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
NativeWidth = () => this.nativeWidth;
@@ -237,6 +244,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.removeDocument = this.props.removeDocument;
dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument;
dragData.dragDivName = this.props.dragDivName;
+ dragData.treeViewId = this.props.treeViewId;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
}
}
@@ -289,13 +297,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
!this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
if (!(e.nativeEvent as any).formattedHandled) {
- const fullScreenAlias = Doc.MakeAlias(this.props.Document);
- if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
- fullScreenAlias.layoutKey = "layout_fullScreen";
+ if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ const func = () => this.onDoubleClickHandler.script.run({
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ }, console.log);
+ func();
+ } else {
+ UndoManager.RunInBatch(() => {
+ if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
+ const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+ fullScreenAlias.layoutKey = "layout_fullScreen";
+ this.props.addDocTab(fullScreenAlias, "inTab");
+ } else {
+ this.props.addDocTab(this.props.Document, "inTab");
+ }
+ }, "double tap");
+ SelectionManager.DeselectAll();
+ Doc.UnBrushDoc(this.props.Document);
}
- UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
- SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this.props.Document);
}
} else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
//SelectionManager.DeselectAll();
@@ -308,7 +329,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
UndoManager.RunInBatch(func, "on click");
} else func();
} else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
- UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
+ const alias = Doc.MakeAlias(this.props.Document);
+ Doc.makeCustomViewClicked(alias, undefined, "onClick");
+ this.props.addDocTab(alias, "onRight");
+ // UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
//ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
} else if (this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
@@ -441,8 +465,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const dY = -1 * Math.sign(dH);
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = PositionDocument(this.props.Document);
- const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
+ const doc = Document(this.props.Document);
+ const layoutDoc = Document(Doc.Layout(this.props.Document));
let nwidth = layoutDoc._nativeWidth || 0;
let nheight = layoutDoc._nativeHeight || 0;
const width = (layoutDoc._width || 0);
@@ -501,7 +525,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) &&
// if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
!((this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0))) {
- if ((this.active || this.Document.onDragStart || this.onClickHandler) &&
+ if ((this.active || this.Document.onDragStart) &&
!e.ctrlKey &&
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
!this.Document.inOverlay) {
@@ -524,12 +548,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (e.cancelBubble && this.active) {
document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
}
- else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.onClickHandler) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart) && !this.Document.lockedPosition && !this.Document.inOverlay) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, this.props.dropAction ? this.props.dropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
}
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -624,18 +648,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- public static unfreezeNativeDimensions(layoutDoc: Doc) {
- layoutDoc._nativeWidth = undefined;
- layoutDoc._nativeHeight = undefined;
- }
-
toggleNativeDimensions = () => {
- if (this.Document._nativeWidth || this.Document._nativeHeight) {
- DocumentView.unfreezeNativeDimensions(this.layoutDoc);
- }
- else {
- Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
- }
+ Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight());
}
@undoBatch
@@ -653,9 +667,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
toggleBackground = (temporary: boolean): void => {
- this.Document.overflow = temporary ? "visible" : "hidden";
+ this.Document._overflow = temporary ? "visible" : "hidden";
this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
- this.Document.isBackground && this.props.bringToFront(this.Document, true);
+ if (this.Document.isBackground) {
+ this.props.bringToFront(this.props.Document, true);
+ this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym]();
+ this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = this.Document[HeightSym]();
+ }
}
@undoBatch
@@ -690,7 +708,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
const cm = ContextMenu.Instance;
- const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
@@ -699,28 +716,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
- let open = cm.findByDescription("Add a Perspective...");
- const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
- openItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
- templateDoc && openItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
- if (!open) {
- open = { description: "Add a Perspective....", subitems: openItems, icon: "external-link-alt" };
- cm.addItem(open);
- }
-
let options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
- optionItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
- optionItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+ const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+ optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
if (!options) {
options = { description: "Options...", subitems: optionItems, icon: "compass" };
cm.addItem(options);
}
- cm.moveAfter(options, open);
-
const existingOnClick = cm.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
@@ -732,6 +737,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+
const funcs: ContextMenuProps[] = [];
if (this.Document.onDragStart) {
funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
@@ -743,16 +749,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const more = cm.findByDescription("More...");
const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- moreItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
+ moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+ moreItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+ moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
if (!ClientUtils.RELEASE) {
// let copies: ContextMenuProps[] = [];
moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
// cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
}
- if (Cast(this.props.Document.data, ImageField)) {
- moreItems.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
- }
if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
@@ -769,6 +774,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// a.download = `DocExport-${this.props.Document[Id]}.zip`;
// a.click();
});
+
const recommender_subitems: ContextMenuProps[] = [];
recommender_subitems.push({
@@ -801,53 +807,52 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+
+ cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
+
runInAction(() => {
- if (!ClientUtils.RELEASE) {
- const setWriteMode = (mode: DocServer.WriteMode) => {
- DocServer.AclsMode = mode;
- const mode1 = mode;
- const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- DocServer.setFieldWriteMode("x", mode1);
- DocServer.setFieldWriteMode("y", mode1);
- DocServer.setFieldWriteMode("_width", mode1);
- DocServer.setFieldWriteMode("_height", mode1);
-
- DocServer.setFieldWriteMode("_panX", mode2);
- DocServer.setFieldWriteMode("_panY", mode2);
- DocServer.setFieldWriteMode("scale", mode2);
- DocServer.setFieldWriteMode("_viewType", mode2);
- };
- const aclsMenu: ContextMenuProps[] = [];
- aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
- aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
- aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
- aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
- cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
- }
+ const setWriteMode = (mode: DocServer.WriteMode) => {
+ DocServer.AclsMode = mode;
+ const mode1 = mode;
+ const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+ DocServer.setFieldWriteMode("x", mode1);
+ DocServer.setFieldWriteMode("y", mode1);
+ DocServer.setFieldWriteMode("_width", mode1);
+ DocServer.setFieldWriteMode("_height", mode1);
+
+ DocServer.setFieldWriteMode("_panX", mode2);
+ DocServer.setFieldWriteMode("_panY", mode2);
+ DocServer.setFieldWriteMode("scale", mode2);
+ DocServer.setFieldWriteMode("_viewType", mode2);
+ };
+ const aclsMenu: ContextMenuProps[] = [];
+ aclsMenu.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
+ aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+ aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+ aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+ aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+ cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
});
runInAction(() => {
- cm.addItem({
- description: "Share",
- event: () => SharingManager.Instance.open(this),
- icon: "external-link-alt"
- });
-
if (!this.topMost && !(e instanceof Touch)) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
}
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- if (!SelectionManager.IsSelected(this, true)) {
- SelectionManager.SelectDoc(this, false);
- }
+ !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
});
const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
- cm.addItem({
+ const item = ({
description: `path: ${path}`, event: () => {
- this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
- Doc.linkFollowHighlight(this.props.Document);
+ if (this.props.LibraryPath !== emptyPath) {
+ this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
+ Doc.linkFollowHighlight(this.props.Document);
+ } else {
+ Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
+ }
}, icon: "check"
});
+ //cm.addItem(item);
}
recommender = async () => {
@@ -971,13 +976,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get contents() {
TraceMobx();
return (<>
- <DocumentContentsView key={1} ContainingCollectionView={this.props.ContainingCollectionView}
+ <DocumentContentsView key={1}
+ ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
NativeWidth={this.NativeWidth}
NativeHeight={this.NativeHeight}
Document={this.props.Document}
DataDoc={this.props.DataDoc}
- LayoutDoc={this.props.LayoutDoc}
+ LayoutTemplateString={this.props.LayoutTemplateString}
+ LayoutTemplate={this.props.LayoutTemplate}
makeLink={this.makeLink}
rootSelected={this.rootSelected}
dontRegisterView={this.props.dontRegisterView}
@@ -990,6 +997,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
renderDepth={this.props.renderDepth}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
+ ignoreAutoHeight={this.props.ignoreAutoHeight}
focus={this.props.focus}
parentActive={this.props.parentActive}
whenActiveChanged={this.props.whenActiveChanged}
@@ -1007,7 +1015,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
</>
);
}
- linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
// used to decide whether a link anchor view should be created or not.
// if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
@@ -1023,7 +1030,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
@undoBatch
- hideLinkAnchor = (doc: Doc) => doc.hidden = true
+ hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true)
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
@computed get anchors() {
@@ -1035,12 +1042,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
- layoutKey={this.linkEndpoint(d)}
ContentScaling={returnOne}
backgroundColor={returnTransparent}
removeDocument={this.hideLinkAnchor}
pointerEvents={false}
- LayoutDoc={undefined}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)}
/>);
}
@computed get innards() {
@@ -1059,9 +1066,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
<div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
<DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
hideOnLeave={true}
- forceLayout={"FormattedTextBox"}
- forceFieldKey={showCaption}
- ContentScaling={this.childScaling}
+ LayoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`}
+ ContentScaling={returnOne}
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
select={this.select}
@@ -1089,7 +1095,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@computed get ignorePointerEvents() {
return this.props.pointerEvents === false ||
- (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) ||
+ (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
(this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
}
@undoBatch
@@ -1100,17 +1106,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
}
}
- @observable _animate = 0;
+ @observable _animateScalingTo = 0;
switchViews = action((custom: boolean, view: string) => {
- SelectionManager.SetIsDragging(true);
- this._animate = 0.1;
+ this._animateScalingTo = 0.1; // shrink doc
setTimeout(action(() => {
this.setCustomView(custom, view);
- this._animate = 1;
- setTimeout(action(() => {
- this._animate = 0;
- SelectionManager.SetIsDragging(false);
- }), 400);
+ this._animateScalingTo = 1; // expand it
+ setTimeout(action(() => this._animateScalingTo = 0), 400);
}), 400);
});
@@ -1128,10 +1130,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
- return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
+ return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+ id={this.props.Document[Id]}
+ ref={this._mainCont} onKeyDown={this.onKeyDown}
onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
- // onPointerEnter={e => Doc.BrushDoc(this.props.Document)}
- // onPointerLeave={e => Doc.BrushDoc(this.props.Document)}
onPointerEnter={action(() => Doc.BrushDoc(this.props.Document))}
onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
let entered = false;
@@ -1144,9 +1146,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
!entered && Doc.UnBrushDoc(this.props.Document);
})}
style={{
- transformOrigin: this._animate ? "center center" : undefined,
- transform: this._animate ? `scale(${this._animate})` : undefined,
- transition: !this._animate ? StrCast(this.Document.transition) : this._animate < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
+ transformOrigin: this._animateScalingTo ? "center center" : undefined,
+ transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
+ transition: !this._animateScalingTo ? StrCast(this.Document.transition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
pointerEvents: this.ignorePointerEvents ? "none" : undefined,
color: StrCast(this.layoutDoc.color, "inherit"),
outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
@@ -1163,7 +1165,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
</> :
this.innards}
{(this.Document.isBackground !== undefined || this.isSelected(false)) && this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
- <div className="documentView-lock" onClick={() => this.toggleBackground(true)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} size="lg" /> </div>
+ <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+ <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
+ </div>
: (null)}
</div>;
{ this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index 3c7f1f206..92ca276cb 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -1,8 +1,8 @@
import React = require("react");
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { Cast, NumCast } from "../../../fields/Types";
import { observer } from "mobx-react";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { Id } from "../../../fields/FieldSymbols";
import FaceRectangle from "./FaceRectangle";
interface FaceRectanglesProps {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 0b9edbcd3..e9dc43bd8 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,11 +1,11 @@
import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { DateField } from "../../../new_fields/DateField";
-import { Doc, FieldResult, Opt, Field } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { AudioField, VideoField } from "../../../new_fields/URLField";
+import { DateField } from "../../../fields/DateField";
+import { Doc, FieldResult, Opt, Field } from "../../../fields/Doc";
+import { List } from "../../../fields/List";
+import { ScriptField } from "../../../fields/ScriptField";
+import { AudioField, VideoField } from "../../../fields/URLField";
import { Transform } from "../../util/Transform";
import { CollectionView } from "../collections/CollectionView";
import { AudioBox } from "./AudioBox";
@@ -31,11 +31,11 @@ export interface FieldViewProps {
select: (isCtrlPressed: boolean) => void;
rootSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
- addDocument?: (document: Doc) => boolean;
+ addDocument?: (document: Doc | Doc[]) => boolean;
addDocTab: (document: Doc, where: string) => boolean;
pinToPres: (document: Doc) => void;
- removeDocument?: (document: Doc) => boolean;
- moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+ removeDocument?: (document: Doc | Doc[]) => boolean;
+ moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
backgroundColor?: (document: Doc) => string | undefined;
ScreenToLocalTransform: () => Transform;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
@@ -43,6 +43,7 @@ export interface FieldViewProps {
whenActiveChanged: (isActive: boolean) => void;
dontRegisterView?: boolean;
focus: (doc: Doc) => void;
+ ignoreAutoHeight?: boolean;
PanelWidth: () => number;
PanelHeight: () => number;
NativeHeight: () => number;
@@ -50,12 +51,13 @@ export interface FieldViewProps {
setVideoBox?: (player: VideoBox) => void;
ContentScaling: () => number;
ChromeHeight?: () => number;
- childLayoutTemplate?: () => Opt<Doc>;
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
height?: number;
width?: number;
background?: string;
color?: string;
+ xMargin?: number;
+ yMargin?: number;
}
@observer
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index c6ea6a882..cf0b16c7c 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -1,15 +1,16 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { createSchema, makeInterface } from '../../../fields/Schema';
import { DocComponent } from '../DocComponent';
import './FontIconBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
-import { StrCast, Cast } from '../../../new_fields/Types';
+import { StrCast, Cast } from '../../../fields/Types';
import { Utils } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
-import { Doc } from '../../../new_fields/Doc';
+import { Doc } from '../../../fields/Doc';
import { ContextMenu } from '../ContextMenu';
+import { ScriptField } from '../../../fields/ScriptField';
const FontIconSchema = createSchema({
icon: "string"
});
@@ -23,7 +24,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
_ref: React.RefObject<HTMLButtonElement> = React.createRef();
_backgroundReaction: IReactionDisposer | undefined;
componentDidMount() {
- this._backgroundReaction = reaction(() => this.props.Document.backgroundColor,
+ this._backgroundReaction = reaction(() => this.layoutDoc.backgroundColor,
() => {
if (this._ref && this._ref.current) {
const col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor);
@@ -35,13 +36,21 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
}
showTemplate = (): void => {
- const dragFactory = Cast(this.props.Document.dragFactory, Doc, null);
+ const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
dragFactory && this.props.addDocTab(dragFactory, "onRight");
}
+ dragAsTemplate = (): void => {
+ this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
+ }
+ useAsPrototype = (): void => {
+ this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)');
+ }
specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
+ cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
+ cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
}
componentWillUnmount() {
@@ -49,12 +58,12 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
}
render() {
- const referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
+ const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc);
const referenceLayout = Doc.Layout(referenceDoc);
- return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
+ return <button className="fontIconBox-outerDiv" title={StrCast(this.layoutDoc.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
style={{
background: StrCast(referenceLayout.backgroundColor),
- boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
+ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
}}>
<FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={this._foregroundColor} size="sm" />
{!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 5)} </div>}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 08917d281..47e7607d6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -4,16 +4,16 @@ import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortaw
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { List } from '../../../new_fields/List';
-import { ObjectField } from '../../../new_fields/ObjectField';
-import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
-import { ComputedField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
-import { AudioField, ImageField } from '../../../new_fields/URLField';
-import { TraceMobx } from '../../../new_fields/util';
+import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
+import { documentSchema } from '../../../fields/documentSchemas';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
+import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { AudioField, ImageField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Docs } from '../../documents/Documents';
@@ -29,6 +29,7 @@ import FaceRectangles from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
const requestImageSize = require('../../util/request-image-size');
const path = require('path');
const { Howl } = require('howler');
@@ -157,21 +158,22 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
- funcs.push({
- description: "Reset Native Dimensions", event: action(async () => {
- const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
- const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
- if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
- this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
- this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
- } else {
- this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
- this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
- }
- }), icon: "expand-arrows-alt"
- });
+ funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+ funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
+ // funcs.push({
+ // description: "Reset Native Dimensions", event: action(async () => {
+ // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+ // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
+ // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
+ // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
+ // } else {
+ // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
+ // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
+ // }
+ // }), icon: "expand-arrows-alt"
+ // });
const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
@@ -181,6 +183,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
!existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+
+ const existingMore = ContextMenu.Instance.findByDescription("More...");
+ const mores: ContextMenuProps[] = existingMore && "subitems" in existingMore ? existingMore.subitems : [];
+ !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" });
}
}
@@ -249,9 +256,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
_curSuffix = "_m";
resize = (imgPath: string) => {
+ const basePath = imgPath.replace(/_[oms]./, "");
const cachedNativeSize = {
- width: imgPath === this.dataDoc[this.fieldKey + "-path"] ? NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]) : 0,
- height: imgPath === this.dataDoc[this.fieldKey + "-path"] ? NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]) : 0,
+ width: basePath === this.dataDoc[this.fieldKey + "-path"] ? NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]) : 0,
+ height: basePath === this.dataDoc[this.fieldKey + "-path"] ? NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]) : 0,
};
const docAspect = this.layoutDoc[HeightSym]() / this.layoutDoc[WidthSym]();
const cachedAspect = cachedNativeSize.height / cachedNativeSize.width;
@@ -265,7 +273,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect;
this.dataDoc[this.fieldKey + "-nativeWidth"] = this.layoutDoc._nativeWidth = this.layoutDoc._width;
this.dataDoc[this.fieldKey + "-nativeHeight"] = this.layoutDoc._nativeHeight = this.layoutDoc._height;
- this.dataDoc[this.fieldKey + "-path"] = imgPath;
+ this.dataDoc[this.fieldKey + "-path"] = basePath;
}
})).catch(console.log);
} else if (Math.abs(1 - docAspect / cachedAspect) > 0.1) {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2970674a2..e983852ea 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,13 +1,13 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Field, FieldResult } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { listSpec } from "../../../new_fields/Schema";
-import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
-import { ImageField } from "../../../new_fields/URLField";
+import { Doc, Field, FieldResult } from "../../../fields/Doc";
+import { List } from "../../../fields/List";
+import { RichTextField } from "../../../fields/RichTextField";
+import { listSpec } from "../../../fields/Schema";
+import { ComputedField, ScriptField } from "../../../fields/ScriptField";
+import { Cast, FieldValue, NumCast } from "../../../fields/Types";
+import { ImageField } from "../../../fields/URLField";
import { Docs } from "../../documents/Documents";
import { SetupDrag } from "../../util/DragManager";
import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting";
@@ -238,8 +238,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
openItems.push({
description: "Default Perspective", event: () => {
- this.props.addDocTab(this.fieldDocToLayout, "inTab");
this.props.addDocTab(this.props.Document, "close");
+ this.props.addDocTab(this.fieldDocToLayout, "onRight");
}, icon: "image"
});
!open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 6dc4ae578..956d6556b 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,6 +1,6 @@
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, Field, Opt } from '../../../new_fields/Doc';
+import { Doc, Field, Opt } from '../../../fields/Doc';
import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 7c7e92379..b605df262 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -4,23 +4,19 @@
border-radius: inherit;
display: flex;
flex-direction: column;
+ position: absolute;
}
.labelBox-mainButton {
- width: 100%;
- height: 100%;
+ width: fit-content;
+ height: max-content;
border-radius: inherit;
letter-spacing: 2px;
text-transform: uppercase;
overflow: hidden;
- display:flex;
-}
-
-.labelBox-mainButtonCenter {
- overflow: hidden;
- display: inline;
- align-items: center;
+ display: inline-block;
margin: auto;
+ text-overflow: ellipsis;
}
.labelBox-params {
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 3cdec8acb..2d27ec441 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -3,11 +3,11 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons';
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { List } from '../../../new_fields/List';
-import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
-import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { documentSchema } from '../../../fields/documentSchemas';
+import { List } from '../../../fields/List';
+import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -80,12 +80,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
paddingRight: NumCast(this.layoutDoc._xPadding),
paddingTop: NumCast(this.layoutDoc._yPadding),
paddingBottom: NumCast(this.layoutDoc._yPadding),
- textOverflow: this.layoutDoc._singleLine ? "ellipsis" : undefined,
- whiteSpace: this.layoutDoc._singleLine ? "nowrap" : "pre-wrap"
+ whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
}} >
- <div className="labelBox-mainButtonCenter">
- {StrCast(this.rootDoc.text, StrCast(this.rootDoc.title))}
- </div>
+ {StrCast(this.rootDoc.text, StrCast(this.rootDoc.title))}
</div>
<div className="labelBox-fieldKeyParams" >
{!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 6c50abf21..098aa58e9 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,9 +1,9 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
@@ -16,8 +16,7 @@ import { ContextMenu } from "../ContextMenu";
import { LinkEditor } from "../linking/LinkEditor";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SelectionManager } from "../../util/SelectionManager";
-import { TraceMobx } from "../../../new_fields/util";
-import { DocumentView } from "./DocumentView";
+import { TraceMobx } from "../../../fields/util";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 740f2ef04..3f942e87b 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,13 +1,13 @@
import React = require("react");
import { observer } from "mobx-react";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface, listSpec } from "../../../new_fields/Schema";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface, listSpec } from "../../../fields/Schema";
import { returnFalse, returnZero } from "../../../Utils";
import { CollectionTreeView } from "../collections/CollectionTreeView";
import { ViewBoxBaseComponent } from "../DocComponent";
import { FieldView, FieldViewProps } from './FieldView';
import "./LinkBox.scss";
-import { Cast } from "../../../new_fields/Types";
+import { Cast } from "../../../fields/Types";
type LinkDocument = makeInterface<[typeof documentSchema]>;
const LinkDocument = makeInterface(documentSchema);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3712c648e..493f23dc4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,11 +3,11 @@ import { action, observable, runInAction, reaction, IReactionDisposer, trace, un
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import { Opt, WidthSym, Doc, HeightSym } from "../../../new_fields/Doc";
-import { makeInterface } from "../../../new_fields/Schema";
-import { ScriptField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { PdfField, URLField } from "../../../new_fields/URLField";
+import { Opt, WidthSym, Doc, HeightSym } from "../../../fields/Doc";
+import { makeInterface } from "../../../fields/Schema";
+import { ScriptField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { PdfField, URLField } from "../../../fields/URLField";
import { Utils } from '../../../Utils';
import { undoBatch } from '../../util/UndoManager';
import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -20,7 +20,7 @@ import { pageSchema } from "./ImageBox";
import { KeyCodes } from '../../util/KeyCodes';
import "./PDFBox.scss";
import React = require("react");
-import { documentSchema } from '../../../new_fields/documentSchemas';
+import { documentSchema } from '../../../fields/documentSchemas';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 78c19f351..d48000e16 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -1,10 +1,10 @@
.presBox-cont {
position: absolute;
+ pointer-events: inherit;
z-index: 2;
box-shadow: #AAAAAA .2vw .2vw .4vw;
- bottom: 0;
width: 100%;
- min-width: 120px;
+ min-width: 20px;
height: 100%;
min-height: 41px;
letter-spacing: 2px;
@@ -17,17 +17,36 @@
width: 100%;
}
.presBox-buttons {
- padding: 10px;
width: 100%;
background: gray;
- padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
+ display: grid;
+ grid-column-end: 4;
+ grid-column-start: 1;
+ .presBox-viewPicker {
+ height: 25;
+ position: relative;
+ display: inline-block;
+ grid-column: 1/2;
+ min-width: 15px;
+ }
+ select {
+ background: #323232;
+ color: white;
+ }
.presBox-button {
margin-right: 2.5%;
margin-left: 2.5%;
- width: 20%;
+ height: 25px;
border-radius: 5px;
+ display: flex;
+ align-items: center;
+ background: #323232;
+ color: white;
+ svg {
+ margin: auto;
+ }
}
.collectionViewBaseChrome-viewPicker {
min-width: 50;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 80d043db1..342a8a215 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1,14 +1,12 @@
import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faHandPointLeft, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, DocCastAsync } from "../../../new_fields/Doc";
-import { InkTool } from "../../../new_fields/InkField";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, DocListCast, DocCastAsync } from "../../../fields/Doc";
+import { InkTool } from "../../../fields/InkField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
import { returnFalse } from "../../../Utils";
-import { documentSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema } from "../../../fields/documentSchemas";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -17,17 +15,12 @@ import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./PresBox.scss";
import { ViewBoxBaseComponent } from "../DocComponent";
-import { makeInterface } from "../../../new_fields/Schema";
-
-library.add(faArrowLeft);
-library.add(faArrowRight);
-library.add(faPlay);
-library.add(faStop);
-library.add(faHandPointLeft);
-library.add(faPlus);
-library.add(faTimes);
-library.add(faMinus);
-library.add(faEdit);
+import { makeInterface } from "../../../fields/Schema";
+import { List } from "../../../fields/List";
+import { Docs } from "../../documents/Documents";
+import { PrefetchProxy } from "../../../fields/Proxy";
+import { ScriptField } from "../../../fields/ScriptField";
+import { Scripting } from "../../util/Scripting";
type PresBoxSchema = makeInterface<[typeof documentSchema]>;
const PresBoxDocument = makeInterface(documentSchema);
@@ -35,58 +28,70 @@ const PresBoxDocument = makeInterface(documentSchema);
@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
- _childReaction: IReactionDisposer | undefined;
@observable _isChildActive = false;
- componentDidMount() {
- this.layoutDoc._forceRenderEngine = "timeline";
- this.layoutDoc._replacedChrome = "replaced";
- this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
- }
- componentWillUnmount() {
- this._childReaction?.();
- }
-
@computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
- @computed get currentIndex() { return NumCast(this.layoutDoc._itemIndex); }
+ @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); }
+ @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); }
+ constructor(props: any) {
+ super(props);
+ if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations.
+ Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
+ title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data"
+ }));
+ // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
+ // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
+ // the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data
+ // stored on each pres element.
+ (this.presElement as Doc).lookupField = ScriptField.MakeFunction("lookupPresBoxField(container, field, data)",
+ { field: "string", data: Doc.name, container: Doc.name });
+ }
+ this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
+ }
- updateCurrentPresentation = action(() => Doc.UserDoc().activePresentation = this.rootDoc);
+ componentDidMount() {
+ this.rootDoc.presBox = this.rootDoc;
+ this.rootDoc._forceRenderEngine = "timeline";
+ this.rootDoc._replacedChrome = "replaced";
+ }
+ updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc;
+ @undoBatch
+ @action
next = () => {
this.updateCurrentPresentation();
- if (this.childDocs[this.currentIndex + 1] !== undefined) {
- let nextSelected = this.currentIndex + 1;
- this.gotoDocument(nextSelected, this.currentIndex);
+ if (this.childDocs[this.itemIndex + 1] !== undefined) {
+ let nextSelected = this.itemIndex + 1;
+ this.gotoDocument(nextSelected, this.itemIndex);
for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) {
if (!this.childDocs[nextSelected].groupButton) {
break;
} else {
- this.gotoDocument(nextSelected, this.currentIndex);
+ this.gotoDocument(nextSelected, this.itemIndex);
}
}
}
}
+
+ @undoBatch
+ @action
back = () => {
this.updateCurrentPresentation();
- const docAtCurrent = this.childDocs[this.currentIndex];
+ const docAtCurrent = this.childDocs[this.itemIndex];
if (docAtCurrent) {
//check if any of the group members had used zooming in including the current document
//If so making sure to zoom out, which goes back to state before zooming action
- let prevSelected = this.currentIndex;
+ let prevSelected = this.itemIndex;
let didZoom = docAtCurrent.zoomButton;
for (; !didZoom && prevSelected > 0 && this.childDocs[prevSelected].groupButton; prevSelected--) {
didZoom = this.childDocs[prevSelected].zoomButton;
}
prevSelected = Math.max(0, prevSelected - 1);
- this.gotoDocument(prevSelected, this.currentIndex);
+ this.gotoDocument(prevSelected, this.itemIndex);
}
}
- whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
- active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
- (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
-
/**
* This is the method that checks for the actions that need to be performed
* after the document has been presented, which involves 3 button options:
@@ -95,15 +100,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
showAfterPresented = (index: number) => {
this.updateCurrentPresentation();
this.childDocs.forEach((doc, ind) => {
+ const presTargetDoc = doc.presentationTargetDoc as Doc;
//the order of cases is aligned based on priority
- if (doc.hideTillShownButton && ind <= index) {
- (doc.presentationTargetDoc as Doc).opacity = 1;
+ if (doc.presHideTillShownButton && ind <= index) {
+ presTargetDoc.opacity = 1;
}
- if (doc.hideAfterButton && ind < index) {
- (doc.presentationTargetDoc as Doc).opacity = 0;
+ if (doc.presHideAfterButton && ind < index) {
+ presTargetDoc.opacity = 0;
}
- if (doc.fadeButton && ind < index) {
- (doc.presentationTargetDoc as Doc).opacity = 0.5;
+ if (doc.presFadeButton && ind < index) {
+ presTargetDoc.opacity = 0.5;
}
});
}
@@ -117,15 +123,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.updateCurrentPresentation();
this.childDocs.forEach((key, ind) => {
//the order of cases is aligned based on priority
-
+ const presTargetDoc = key.presentationTargetDoc as Doc;
if (key.hideAfterButton && ind >= index) {
- (key.presentationTargetDoc as Doc).opacity = 1;
+ presTargetDoc.opacity = 1;
}
if (key.fadeButton && ind >= index) {
- (key.presentationTargetDoc as Doc).opacity = 1;
+ presTargetDoc.opacity = 1;
}
if (key.hideTillShownButton && ind > index) {
- (key.presentationTargetDoc as Doc).opacity = 0;
+ presTargetDoc.opacity = 0;
}
});
}
@@ -151,11 +157,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
currentDocGroups.forEach((doc: Doc, index: number) => {
- if (doc.navButton) {
+ if (doc.presNavButton) {
docToJump = doc;
willZoom = false;
}
- if (doc.zoomButton) {
+ if (doc.presZoomButton) {
docToJump = doc;
willZoom = true;
}
@@ -166,10 +172,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const srcContext = aliasOf && await DocCastAsync(aliasOf.context);
if (docToJump === curDoc) {
//checking if curDoc has navigation open
- const target = await DocCastAsync(curDoc.presentationTargetDoc);
- if (curDoc.navButton && target) {
+ const target = (await DocCastAsync(curDoc.presentationTargetDoc)) || curDoc;
+ if (curDoc.presNavButton && target) {
DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext);
- } else if (curDoc.zoomButton && target) {
+ } else if (curDoc.presZoomButton && target) {
//awaiting jump so that new scale can be found, since jumping is async
await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext);
}
@@ -180,19 +186,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
}
-
- @undoBatch
- public removeDocument = (doc: Doc) => {
- return Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
- }
-
//The function that is called when a document is clicked or reached through next or back.
//it'll also execute the necessary actions if presentation is playing.
public gotoDocument = (index: number, fromDoc: number) => {
this.updateCurrentPresentation();
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
- this.layoutDoc._itemIndex = index;
+ this.rootDoc._itemIndex = index;
if (!this.layoutDoc.presStatus) {
this.layoutDoc.presStatus = true;
@@ -213,23 +213,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
} else {
this.layoutDoc.presStatus = true;
this.startPresentation(0);
- this.gotoDocument(0, this.currentIndex);
+ this.gotoDocument(0, this.itemIndex);
}
}
- addDocument = (doc: Doc) => {
- const newPinDoc = Doc.MakeAlias(doc);
- newPinDoc.presentationTargetDoc = doc;
- return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc);
- }
-
-
//The function that resets the presentation by removing every action done by it. It also
//stops the presentaton.
resetPresentation = () => {
this.updateCurrentPresentation();
this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1);
- this.layoutDoc._itemIndex = 0;
+ this.rootDoc._itemIndex = 0;
this.layoutDoc.presStatus = false;
}
@@ -238,89 +231,95 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
startPresentation = (startIndex: number) => {
this.updateCurrentPresentation();
this.childDocs.map(doc => {
- if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) {
- (doc.presentationTargetDoc as Doc).opacity = 0;
+ const presTargetDoc = doc.presentationTargetDoc as Doc;
+ if (doc.presHideTillShownButton && this.childDocs.indexOf(doc) > startIndex) {
+ presTargetDoc.opacity = 0;
}
- if (doc.hideAfterButton && this.childDocs.indexOf(doc) < startIndex) {
- (doc.presentationTargetDoc as Doc).opacity = 0;
+ if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) {
+ presTargetDoc.opacity = 0;
}
- if (doc.fadeButton && this.childDocs.indexOf(doc) < startIndex) {
- (doc.presentationTargetDoc as Doc).opacity = 0.5;
+ if (doc.presFadeButton && this.childDocs.indexOf(doc) < startIndex) {
+ presTargetDoc.opacity = 0.5;
}
});
}
- updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: CollectionViewType) => {
+ updateMinimize = action((e: React.ChangeEvent, mode: CollectionViewType) => {
if (BoolCast(this.layoutDoc.inOverlay) !== (mode === CollectionViewType.Invalid)) {
if (this.layoutDoc.inOverlay) {
Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc);
CollectionDockingView.AddRightSplit(this.rootDoc);
this.layoutDoc.inOverlay = false;
} else {
- this.layoutDoc.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25;
- this.layoutDoc.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25;
+ const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ this.rootDoc.x = pt[0];// 500;//e.clientX + 25;
+ this.rootDoc.y = pt[1];////e.clientY - 25;
this.props.addDocTab?.(this.rootDoc, "close");
Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc);
}
}
- }));
-
- initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => {
- const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
- docList.forEach(doc => {
- doc.presBox = this.rootDoc; // give contained documents a reference to the presentation
- doc.collapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din
- });
- }
-
- selectElement = (doc: Doc) => {
- this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.layoutDoc._itemIndex));
- }
-
- getTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
- }
- panelHeight = () => {
- return this.props.PanelHeight() - 20;
- }
+ });
@undoBatch
viewChanged = action((e: React.ChangeEvent) => {
//@ts-ignore
- this.layoutDoc._viewType = e.target.selectedOptions[0].value;
- this.layoutDoc._viewType === CollectionViewType.Stacking && (this.layoutDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- this.updateMinimize(e, StrCast(this.layoutDoc._viewType));
+ const viewType = e.target.selectedOptions[0].value as CollectionViewType;
+ viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
+ this.updateMinimize(e, this.rootDoc._viewType = viewType);
});
- childLayoutTemplate = () => this.layoutDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined;
+ whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
+ addDocumentFilter = (doc: Doc | Doc[]) => {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ docs.forEach(doc => {
+ doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
+ !this.childDocs.includes(doc) && (doc.presZoomButton = true);
+ });
+ return true;
+ }
+ childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
+ removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
+ selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex));
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
+ panelHeight = () => this.props.PanelHeight() - 20;
+ active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
+ (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+
render() {
- const mode = StrCast(this.layoutDoc._viewType) as CollectionViewType;
- this.initializeViewAliases(this.childDocs, mode);
- return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.layoutDoc.inOverlay ? "all" : "none" }} >
- <div className="presBox-buttons" style={{ display: this.layoutDoc._chromeStatus === "disabled" ? "none" : undefined }}>
- <select className="collectionViewBaseChrome-viewPicker"
+ this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.map((child, i) => child));
+ const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
+ return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
+ <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
+ <select className="presBox-viewPicker"
onPointerDown={e => e.stopPropagation()}
onChange={this.viewChanged}
value={mode}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Invalid}>Min</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Time}>Time</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Invalid}>Min</option>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Time}>Time</option>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option>
</select>
- <button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
- <button className="presBox-button" title={"Reset Presentation" + this.layoutDoc.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}>
+ <div className="presBox-button" title="Back" style={{ gridColumn: 2 }} onClick={this.back}>
+ <FontAwesomeIcon icon={"arrow-left"} />
+ </div>
+ <div className="presBox-button" title={"Reset Presentation" + this.layoutDoc.presStatus ? "" : " From Start"} style={{ gridColumn: 3 }} onClick={this.startOrResetPres}>
<FontAwesomeIcon icon={this.layoutDoc.presStatus ? "stop" : "play"} />
- </button>
- <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
+ </div>
+ <div className="presBox-button" title="Next" style={{ gridColumn: 4 }} onClick={this.next}>
+ <FontAwesomeIcon icon={"arrow-right"} />
+ </div>
</div>
<div className="presBox-listCont" >
{mode !== CollectionViewType.Invalid ?
<CollectionView {...this.props}
+ ContainingCollectionDoc={this.props.Document}
+ PanelWidth={this.props.PanelWidth}
PanelHeight={this.panelHeight}
moveDocument={returnFalse}
childLayoutTemplate={this.childLayoutTemplate}
- addDocument={this.addDocument}
+ filterAddDocument={this.addDocumentFilter}
removeDocument={returnFalse}
+ dontRegisterView={true}
focus={this.selectElement}
ScreenToLocalTransform={this.getTransform} />
: (null)
@@ -328,4 +327,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
</div>;
}
-} \ No newline at end of file
+}
+Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
+ if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
+ if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46;
+ if (field === 'presStatus') return container.presStatus;
+ if (field === '_itemIndex') return container._itemIndex;
+ return undefined;
+});
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 76885eada..0fff0b57f 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -1,16 +1,16 @@
import React = require("react");
import { IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Id } from '../../../new_fields/FieldSymbols';
-import { makeInterface, listSpec } from "../../../new_fields/Schema";
-import { StrCast, Cast } from "../../../new_fields/Types";
-import { SelectionManager } from "../../util/SelectionManager";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from '../../../fields/FieldSymbols';
+import { makeInterface, listSpec } from "../../../fields/Schema";
+import { StrCast, Cast } from "../../../fields/Types";
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { SearchBox } from "../search/SearchBox";
import { FieldView, FieldViewProps } from './FieldView';
import "./QueryBox.scss";
-import { List } from "../../../new_fields/List";
+import { List } from "../../../fields/List";
+import { SnappingManager } from "../../util/SnappingManager";
type QueryDocument = makeInterface<[typeof documentSchema]>;
const QueryDocument = makeInterface(documentSchema);
@@ -27,7 +27,7 @@ export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryD
}
render() {
- const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
+ const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging";
return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
<SearchBox
id={this.props.Document[Id]}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 125690dc7..5d4af2d77 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -5,10 +5,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as rp from 'request-promise';
-import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast } from "../../../new_fields/Types";
-import { VideoField } from "../../../new_fields/URLField";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import { Cast, NumCast } from "../../../fields/Types";
+import { VideoField } from "../../../fields/URLField";
import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -20,8 +20,8 @@ import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
const path = require('path');
-type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
+type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
+const ScreenshotDocument = makeInterface(documentSchema);
library.add(faVideo);
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 467dd064c..98d5e6b71 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,10 +1,10 @@
import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { StrCast, ScriptCast, Cast } from "../../../new_fields/Types";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { createSchema, makeInterface, listSpec } from "../../../fields/Schema";
+import { ScriptField } from "../../../fields/ScriptField";
+import { StrCast, ScriptCast, Cast } from "../../../fields/Types";
import { InteractionUtils } from "../../util/InteractionUtils";
import { CompileScript, isCompileError, ScriptParam } from "../../util/Scripting";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
@@ -13,8 +13,12 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./ScriptingBox.scss";
import { OverlayView } from "../OverlayView";
import { DocumentIconContainer } from "./DocumentIcon";
+<<<<<<< HEAD
import { List } from "../../../new_fields/List";
import { DragManager } from "../../util/DragManager";
+=======
+import { List } from "../../../fields/List";
+>>>>>>> 98c7540fff67c232c1b04f2130ee624f9a70afbd
const ScriptingSchema = createSchema({});
type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>;
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index cb2526769..9a1aefba9 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -4,10 +4,10 @@ import { runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { createSchema, makeInterface } from '../../../new_fields/Schema';
-import { ScriptField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { documentSchema } from '../../../fields/documentSchemas';
+import { createSchema, makeInterface } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxBaseComponent } from '../DocComponent';
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 613929bca..ccf1f5588 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -5,12 +5,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
import * as rp from 'request-promise';
-import { Doc } from "../../../new_fields/Doc";
-import { InkTool } from "../../../new_fields/InkField";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../../new_fields/Types";
-import { VideoField } from "../../../new_fields/URLField";
+import { Doc } from "../../../fields/Doc";
+import { InkTool } from "../../../fields/InkField";
+import { createSchema, makeInterface } from "../../../fields/Schema";
+import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, StrCast } from "../../../fields/Types";
+import { VideoField } from "../../../fields/URLField";
import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -21,14 +21,14 @@ import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
-import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema } from "../../../fields/documentSchemas";
const path = require('path');
export const timeSchema = createSchema({
currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first
});
-type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
-const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
+type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>;
+const VideoDocument = makeInterface(documentSchema, timeSchema);
library.add(faVideo);
@@ -337,9 +337,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
@action.bound
- addDocumentWithTimestamp(doc: Doc): boolean {
- const curTime = (this.layoutDoc.currentTimecode || -1);
- curTime !== -1 && (doc.displayTimecode = curTime);
+ addDocumentWithTimestamp(doc: Doc | Doc[]): boolean {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ docs.forEach(doc => {
+ const curTime = (this.layoutDoc.currentTimecode || -1);
+ curTime !== -1 && (doc.displayTimecode = curTime);
+ });
return this.addDocument(doc);
}
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index af84a7d95..4623444b9 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -3,6 +3,38 @@
.webBox-container, .webBox-container-dragging {
transform-origin: top left;
+ width: 100%;
+ height: 100%;
+
+ .webBox-htmlSpan {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ .webBox-cont {
+ pointer-events: none;
+ }
+ .webBox-cont, .webBox-cont-interactive {
+ padding: 0vw;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform-origin: top left;
+ overflow: auto;
+ .webBox-iframe {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top:0;
+ }
+ }
+ .webBox-cont-interactive {
+ span {
+ user-select: text !important;
+ }
+ }
.webBox-outerContent {
width: 100%;
height: 100%;
@@ -18,29 +50,7 @@
display:none;
}
}
-.webBox-cont {
- padding: 0vw;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- transform-origin: top left;
- overflow: auto;
- pointer-events: none;
-}
-
-.webBox-cont-interactive {
- span {
- user-select: text !important;
- }
-}
-#webBox-htmlSpan {
- position: absolute;
- top: 0;
- left: 0;
-}
.webBox-overlay {
width: 100%;
@@ -66,8 +76,6 @@
opacity: 0.9;
z-index: 9001;
transition: top .5s;
- padding: 10px;
-
.urlEditor {
display: grid;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 4e383e468..82f05012a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -2,13 +2,13 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons';
import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, FieldResult } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { HtmlField } from "../../../new_fields/HtmlField";
-import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
-import { WebField } from "../../../new_fields/URLField";
+import { Doc, FieldResult } from "../../../fields/Doc";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { HtmlField } from "../../../fields/HtmlField";
+import { InkTool } from "../../../fields/InkField";
+import { makeInterface } from "../../../fields/Schema";
+import { Cast, NumCast, BoolCast, StrCast } from "../../../fields/Types";
+import { WebField } from "../../../fields/URLField";
import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
@@ -48,10 +48,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
iframeLoaded = action((e: any) => {
- this._iframeRef.current!.contentDocument?.addEventListener('pointerdown', this.iframedown, false);
- this._iframeRef.current!.contentDocument?.addEventListener('scroll', this.iframeScrolled, false);
- this.layoutDoc.scrollHeight = this._iframeRef.current!.contentDocument?.children?.[0].scrollHeight || 1000;
- this._iframeRef.current!.contentDocument!.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+ if (this._iframeRef.current?.contentDocument) {
+ this._iframeRef.current.contentDocument.addEventListener('pointerdown', this.iframedown, false);
+ this._iframeRef.current.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
+ this.layoutDoc.scrollHeight = this._iframeRef.current.contentDocument.children?.[0].scrollHeight || 1000;
+ this._iframeRef.current.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+ }
this._reactionDisposer?.();
this._reactionDisposer = reaction(() => this.layoutDoc.scrollY,
(scrollY) => {
@@ -127,28 +129,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
}
- toggleNativeDimensions = () => {
+ toggleAnnotationMode = () => {
if (!this.layoutDoc.isAnnotating) {
- //DocumentView.unfreezeNativeDimensions(this.layoutDoc);
this.layoutDoc.lockedTransform = false;
this.layoutDoc.isAnnotating = true;
}
else {
- //Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
this.layoutDoc.lockedTransform = true;
this.layoutDoc.isAnnotating = false;
}
}
urlEditor() {
- const frozen = this.layoutDoc._nativeWidth && this.layoutDoc.isAnnotating;
return (
<div className="webBox-urlEditor" style={{ top: this._collapsed ? -70 : 0 }}>
<div className="urlEditor">
<div className="editorBase">
<button className="editor-collapse"
style={{
- top: this._collapsed ? 70 : 10,
+ top: this._collapsed ? 70 : 0,
transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`,
opacity: (this._collapsed && !this.props.isSelected()) ? 0 : 0.9,
left: (this._collapsed ? 0 : "unset"),
@@ -157,10 +156,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
<FontAwesomeIcon icon="caret-up" size="2x" />
</button>
<div className="webBox-buttons" style={{ display: this._collapsed ? "none" : "flex" }}>
- <div className="webBox-freeze" title={"Annotate"} style={{ background: frozen ? "lightBlue" : "gray" }} onClick={this.toggleNativeDimensions} >
+ <div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
<FontAwesomeIcon icon={faPen} size={"2x"} />
</div>
- <div className="webBox-freeze" title={"Select"} style={{ background: !frozen ? "lightBlue" : "gray" }} onClick={this.toggleNativeDimensions} >
+ <div className="webBox-freeze" title={"Select"} style={{ background: !this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
<FontAwesomeIcon icon={faMousePointer} size={"2x"} />
</div>
<input className="webpage-urlInput"
@@ -309,33 +308,35 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
}
+ //const href = "https://brown365-my.sharepoint.com/personal/bcz_ad_brown_edu/_layouts/15/Doc.aspx?sourcedoc={31aa3178-4c21-4474-b367-877d0a7135e4}&action=embedview&wdStartOn=1";
@computed
- get content() {
+ get urlContent() {
+
const field = this.dataDoc[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
- view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+ 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;
- view = <iframe ref={this._iframeRef} onLoad={this.iframeLoaded} src={url} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+ view = <iframe className="webBox-iframe" ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} />;
} else {
- view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+ view = <iframe className="webBox-iframe" ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />;
}
- const content =
- <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- {this.urlEditor()}
- {view}
- </div>;
-
+ return view;
+ }
+ @computed
+ get content() {
+ const view = this.urlContent;
const decInteracting = DocumentDecorations.Instance?.Interacting;
const frozen = !this.props.isSelected() || decInteracting;
return (<>
- <div className={"webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "")} >
- {content}
- </div>
+ <div className={"webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "")}
+ onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
+ {view}
+ </div>;
{!frozen ? (null) :
<div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }}
onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
@@ -344,6 +345,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
<div className="dragger" ref={this._iframeDragRef}></div>
</div>
</div>}
+ {this.urlEditor()}
</>);
}
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.Document.scrollTop));
@@ -351,8 +353,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
return (<div className={`webBox-container`}
style={{
transform: `scale(${this.props.ContentScaling()})`,
- width: `${100 / this.props.ContentScaling()}%`,
- height: `${100 / this.props.ContentScaling()}%`,
+ width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
+ height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
}} >
{this.content}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index d94fe7fc6..d56b87ae5 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -8,14 +8,14 @@ import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-s
import { StepMap } from "prosemirror-transform";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { List } from "../../../../new_fields/List";
-import { ObjectField } from "../../../../new_fields/ObjectField";
-import { listSpec } from "../../../../new_fields/Schema";
-import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { List } from "../../../../fields/List";
+import { ObjectField } from "../../../../fields/ObjectField";
+import { listSpec } from "../../../../fields/Schema";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 7130fee2b..05e6a5959 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,10 +1,10 @@
import { IReactionDisposer, reaction } from "mobx";
import { NodeSelection } from "prosemirror-state";
-import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { ObjectField } from "../../../../new_fields/ObjectField";
-import { ComputedField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { ObjectField } from "../../../../fields/ObjectField";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 422710c3e..d05e8f1ea 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,10 +1,10 @@
import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
-import { Doc, DocListCast, Field } from "../../../../new_fields/Doc";
-import { List } from "../../../../new_fields/List";
-import { listSpec } from "../../../../new_fields/Schema";
-import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../../../new_fields/Types";
+import { Doc, DocListCast, Field } from "../../../../fields/Doc";
+import { List } from "../../../../fields/List";
+import { listSpec } from "../../../../fields/Schema";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { Cast, StrCast } from "../../../../fields/Types";
import { DocServer } from "../../../DocServer";
import { CollectionViewType } from "../../collections/CollectionView";
import { FormattedTextBox } from "./FormattedTextBox";
@@ -34,8 +34,7 @@ export class DashFieldView {
docid={node.attrs.docid}
width={node.attrs.width}
height={node.attrs.height}
- view={view}
- getPos={getPos}
+ hideKey={node.attrs.hideKey}
tbox={tbox}
/>, this._fieldWrapper);
(this as any).dom = this._fieldWrapper;
@@ -49,8 +48,7 @@ export class DashFieldView {
interface IDashFieldViewInternal {
fieldKey: string;
docid: string;
- view: any;
- getPos: any;
+ hideKey: boolean;
tbox: FormattedTextBox;
width: number;
height: number;
@@ -81,11 +79,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._reactionDisposer?.();
}
+ multiValueDelimeter = ";";
+
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
if (this._dashDoc) {
- const dashVal = this._dashDoc[this._fieldKey];
- const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
+ const dashVal = this._dashDoc[this._fieldKey] || (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
+ const fval = dashVal instanceof List ? dashVal.join(this.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
const boolVal = Cast(fval, "boolean", null);
const strVal = Field.toString(fval as Field) || "";
@@ -108,7 +108,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
}} >
{strVal}
- </span>
+ </span>;
}
}
}
@@ -155,7 +155,10 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
} else if (nodeText.startsWith("=:=")) {
Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
} else {
- this._dashDoc![this._fieldKey] = newText;
+ const splits = newText.split(this.multiValueDelimeter);
+ if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+ this._dashDoc![this._fieldKey] = splits.length > 1 ? new List<string>(splits) : newText;
+ }
}
});
}
@@ -196,9 +199,10 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
width: this.props.width,
height: this.props.height,
}}>
- <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {this._fieldKey}
- </span>
+ {this.props.hideKey ? (null) :
+ <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
+ {this._fieldKey}
+ </span>}
<div className="dashFieldView-fieldSpan">
{this.fieldValueContent}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 782a91547..5e33e7e70 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,18 +12,18 @@ import { Fragment, Mark, Node, Slice } from "prosemirror-model";
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
-import { DateField } from '../../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
-import { documentSchema } from '../../../../new_fields/documentSchemas';
-import { Id } from '../../../../new_fields/FieldSymbols';
-import { InkTool } from '../../../../new_fields/InkField';
-import { PrefetchProxy } from '../../../../new_fields/Proxy';
-import { RichTextField } from "../../../../new_fields/RichTextField";
-import { RichTextUtils } from '../../../../new_fields/RichTextUtils';
-import { createSchema, makeInterface } from "../../../../new_fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
-import { TraceMobx } from '../../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../../Utils';
+import { DateField } from '../../../../fields/DateField';
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from '../../../../fields/FieldSymbols';
+import { InkTool } from '../../../../fields/InkField';
+import { PrefetchProxy } from '../../../../fields/Proxy';
+import { RichTextField } from "../../../../fields/RichTextField";
+import { RichTextUtils } from '../../../../fields/RichTextUtils';
+import { createSchema, makeInterface } from "../../../../fields/Schema";
+import { Cast, DateCast, NumCast, StrCast } from "../../../../fields/Types";
+import { TraceMobx } from '../../../../fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from '../../../documents/Documents';
@@ -58,19 +58,21 @@ import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
+import { ScriptField } from '../../../../fields/ScriptField';
+import GoogleAuthenticationManager from '../../../apis/GoogleAuthenticationManager';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
export interface FormattedTextBoxProps {
- hideOnLeave?: boolean;
- makeLink?: () => Opt<Doc>;
- xMargin?: number;
+ makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
+ hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
+ xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yMargin?: number;
}
const richTextSchema = createSchema({
- documentText: "string"
+ documentText: "string",
});
export const GoogleRef = "googleDocId";
@@ -91,9 +93,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _editorView: Opt<EditorView>;
private _applyingChange: boolean = false;
private _searchIndex = 0;
- private _sidebarMovement = 0;
- private _lastX = 0;
- private _lastY = 0;
private _undoTyping?: UndoManager.Batch;
private _disposers: { [name: string]: IReactionDisposer } = {};
private dropDisposer?: DragManager.DragDropDisposer;
@@ -159,7 +158,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
- else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+ else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
});
});
});
@@ -197,15 +196,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
- if (!this._applyingChange) {
+ const curTemp = Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField); // the actual text in the text box
+ const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
+ const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
+ const json = JSON.stringify(state.toJSON());
+ if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
this._applyingChange = true;
this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText);
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+ if (json !== curLayout?.Data) {
+ !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
+ this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
+ this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ }
} else { // if we've deleted all the text in a note driven by a template, then restore the template data
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
+ this.dataDoc[this.props.fieldKey] = undefined;
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
}
this._applyingChange = false;
@@ -233,7 +239,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
res.map(r => r.map(h => flattened.push(h)));
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
- const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!;
+ const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
const link = this._editorView.state.schema.marks.link.create({
href: Utils.prepend("/doc/" + alink[Id]),
title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
@@ -272,7 +278,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
protected createDropTarget = (ele: HTMLDivElement) => {
this.ProseRef = ele;
this.dropDisposer?.();
- ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+ ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
}
@undoBatch
@@ -391,26 +397,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
sidebarDown = (e: React.PointerEvent) => {
- this._lastX = e.clientX;
- this._lastY = e.clientY;
- this._sidebarMovement = 0;
- document.addEventListener("pointermove", this.sidebarMove);
- document.addEventListener("pointerup", this.sidebarUp);
- e.stopPropagation();
- e.preventDefault(); // prevents text from being selected during drag
+ setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction,
+ () => (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"));
}
- sidebarMove = (e: PointerEvent) => {
+ sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const bounds = this.CurrentDiv.getBoundingClientRect();
- this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
- this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
+ this.layoutDoc._sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
+ return false;
}
- sidebarUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.sidebarMove);
- document.removeEventListener("pointerup", this.sidebarUp);
+ @undoBatch
+ @action
+ toggleNativeDimensions = () => {
+ Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
}
- toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
-
public static get DefaultLayout(): Doc | string | undefined {
return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
}
@@ -418,19 +418,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
- this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
- !this.rootDoc.isTemplateDoc && funcs.push({ description: "Show Template", event: async () => this.props.addDocTab(Doc.GetProto(this.layoutDoc), "onRight"), icon: "eye" });
- funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
- !this.rootDoc.isTemplateDoc && funcs.push({
- description: "Make Template", event: () => {
- this.props.Document.isTemplateDoc = makeTemplate(this.props.Document);
- Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
+ this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
+ !this.layoutDoc.isTemplateDoc && funcs.push({
+ description: "Convert to use as a style", event: () => {
+ this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc);
+ Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
}, icon: "eye"
});
- funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
- funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
- funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
- funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+ this.layoutDoc.isTemplateDoc && funcs.push({
+ description: "Make New Template", event: () => {
+ const title = this.rootDoc.title as string;
+ this.rootDoc.layout = (this.layoutDoc as Doc).layout as string;
+ this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
+ this.rootDoc.isTemplateDoc = false;
+ this.rootDoc.isTemplateForField = "";
+ this.rootDoc.layoutKey = "layout";
+ this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
+ setTimeout(() => {
+ this.rootDoc._width = this.layoutDoc._width || 300; // the width and height are stored on the template, since we're getting rid of the old template
+ this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
+ this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
+ }, 10);
+ Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
+ }, icon: "eye"
+ });
+ //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+ funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
+
+ const uicontrols: ContextMenuProps[] = [];
+ uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
+ uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
+ uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+ funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
const highlighting: ContextMenuProps[] = [];
["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
@@ -456,26 +477,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DocListCast(noteTypesDoc?.data).forEach(note => {
changeItems.push({
description: StrCast(note.title), event: undoBatch(() => {
- Doc.setNativeView(this.props.Document);
+ Doc.setNativeView(this.rootDoc);
Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
}), icon: "eye"
});
});
changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
!change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
-
- const open = cm.findByDescription("Add a Perspective...");
- const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-
- openItems.push({
- description: "FreeForm", event: undoBatch(() => {
- const alias = Doc.MakeAlias(this.rootDoc);
- Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
- this.props.addDocTab(alias, "onRight");
- }), icon: "eye"
- });
- !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
-
}
recordDictation = () => {
@@ -493,7 +501,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@action
toggleMenubar = () => {
- this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
+ this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled";
}
recordBullet = async () => {
@@ -606,10 +614,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
);
this._disposers.editorState = reaction(
() => {
- if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
+ if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
}
- return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
+ return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
},
incomingValue => {
if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
@@ -638,9 +646,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
- this._disposers.height = reaction(
+ this._disposers.autoHeight = reaction(
() => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
- () => this.tryUpdateHeight()
+ () => setTimeout(() => this.tryUpdateHeight(), 0)
+ );
+ this._disposers.height = reaction(
+ () => this.layoutDoc[HeightSym](),
+ action(height => {
+ if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
+ this.layoutDoc._delayAutoHeight = height;
+ }
+ })
);
this.setupEditor(this.config, this.props.fieldKey);
@@ -666,7 +682,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const nodes: Node[] = [];
frag.forEach((node, index) => {
const examinedNode = findLinkNode(node, editor);
- if (examinedNode && examinedNode.textContent) {
+ if (examinedNode?.textContent) {
nodes.push(examinedNode);
start += index;
}
@@ -683,7 +699,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
};
- let start = -1;
+ let start = 0;
if (this._editorView && scrollToLinkID) {
const editor = this._editorView;
const ret = findLinkFrag(editor.state.doc.content, editor);
@@ -704,7 +720,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
},
{ fireImmediately: true }
);
- this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos),
+ this._disposers.scroll = reaction(() => NumCast(this.layoutDoc.scrollPos),
pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
);
@@ -761,7 +777,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let pullSuccess = false;
if (exportState !== undefined) {
pullSuccess = true;
- dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
+ dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON()));
setTimeout(() => {
if (this._editorView) {
const state = this._editorView.state;
@@ -823,7 +839,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
targetAnnotations?.push(pdfRegion);
});
- const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted");
+ const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted");
if (link) {
cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
const linkId = link[Id];
@@ -860,8 +876,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
- const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"];
- const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
+ const useTemplate = !curText?.Text && this.layoutDoc[this.props.fieldKey + "-textTemplate"];
+ const rtfField = Cast((useTemplate && this.layoutDoc[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
if (this.ProseRef) {
const self = this;
this._editorView?.destroy();
@@ -957,9 +973,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
e.preventDefault();
}
- if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
- e.stopPropagation();
+ if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+ e.stopPropagation(); // if the text box is selected, then it consumes all down events
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
@@ -997,7 +1013,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// jump rich text menu to this textbox
const bounds = this._ref.current?.getBoundingClientRect();
- if (bounds && this.props.Document._chromeStatus !== "disabled") {
+ if (bounds && this.layoutDoc._chromeStatus !== "disabled") {
const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
@@ -1033,43 +1049,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
(e.nativeEvent as any).formattedHandled = true;
- // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
- // let href = (e.target as any).href;
- // let location: string;
- // if ((e.target as any).attributes.location) {
- // location = (e.target as any).attributes.location.value;
- // }
- // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
- // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
- // if (node) {
- // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
- // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
- // href = link && link.attrs.href;
- // location = link && link.attrs.location;
- // }
- // }
- // if (href) {
- // if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- // if (linkClicked) {
- // DocServer.GetRefField(linkClicked).then(async linkDoc => {
- // (linkDoc instanceof Doc) &&
- // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false);
- // });
- // }
- // } else {
- // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
- // this.props.addDocument && this.props.addDocument(webDoc);
- // }
- // e.stopPropagation();
- // e.preventDefault();
- // }
- // }
if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
this.props.select(e.ctrlKey);
this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
}
+ if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
+ e.stopPropagation();
+ }
}
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
@@ -1186,34 +1173,44 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onscrolled = (ev: React.UIEvent) => {
- this.props.Document.scrollPos = this._scrollRef.current!.scrollTop;
+ this.layoutDoc.scrollPos = this._scrollRef.current!.scrollTop;
}
@action
tryUpdateHeight(limitHeight?: number) {
let scrollHeight = this._ref.current?.scrollHeight;
- if (this.layoutDoc._autoHeight && scrollHeight &&
- getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ if (this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ scrollHeight = scrollHeight * NumCast(this.layoutDoc.scale, 1);
if (limitHeight && scrollHeight > limitHeight) {
scrollHeight = limitHeight;
this.layoutDoc.limitHeight = undefined;
this.layoutDoc._autoHeight = false;
}
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
- const dh = NumCast(this.layoutDoc._height, 0);
+ const dh = NumCast(this.rootDoc._height, 0);
const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
- this.layoutDoc._height = newHeight;
- this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
+ if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
+ // 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...
+ console.log("Delayed height adjustment...");
+ setTimeout(() => {
+ this.rootDoc._height = newHeight;
+ this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
+ }, 10);
+ } else {
+ this.rootDoc._height = newHeight;
+ this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
+ }
}
}
}
- @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
@computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
render() {
TraceMobx();
+ const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
@@ -1222,82 +1219,90 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBoxComment.Hide();
}
return (
-
- <div className={`formattedTextBox-cont`} ref={this._ref}
- style={{
- height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
- background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
- opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
- color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
- pointerEvents: interactive ? "none" : undefined,
- fontSize: Cast(this.layoutDoc._fontSize, "number", null),
- fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
- }}
- onContextMenu={this.specificContextMenu}
- onKeyDown={this.onKeyPress}
- onFocus={this.onFocused}
- onClick={this.onClick}
- onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
- onBlur={this.onBlur}
- onPointerUp={this.onPointerUp}
- onPointerDown={this.onPointerDown}
- onMouseUp={this.onMouseUp}
- onWheel={this.onPointerWheel}
- onPointerEnter={action(() => this._entered = true)}
- onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
- this._entered = false;
- const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
- for (let child: any = target; child; child = child?.parentElement) {
- if (child === this._ref.current!) {
- this._entered = true;
+ <div className={"formattedTextBox-cont"} style={{
+ transform: `scale(${scale})`,
+ transformOrigin: "top left",
+ width: `${100 / scale}%`,
+ height: `${100 / scale}%`,
+ ...this.styleFromLayoutString(scale)
+ }}>
+ <div className={`formattedTextBox-cont`} ref={this._ref}
+ style={{
+ width: "100%",
+ height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
+ background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
+ opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
+ color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
+ pointerEvents: interactive ? "none" : undefined,
+ fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+ fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit")
+ }}
+ onContextMenu={this.specificContextMenu}
+ onKeyDown={this.onKeyPress}
+ onFocus={this.onFocused}
+ onClick={this.onClick}
+ onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
+ onBlur={this.onBlur}
+ onPointerUp={this.onPointerUp}
+ onPointerDown={this.onPointerDown}
+ onMouseUp={this.onMouseUp}
+ onWheel={this.onPointerWheel}
+ onPointerEnter={action(() => this._entered = true)}
+ onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
+ this._entered = false;
+ const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
+ for (let child: any = target; child; child = child?.parentElement) {
+ if (child === this._ref.current!) {
+ this._entered = true;
+ }
}
- }
- })}
- >
- <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
- <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
- style={{
- padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
- pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
- }} />
+ })}
+ >
+ <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
+ <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+ style={{
+ padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
+ pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+ }} />
+ </div>
+ {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> :
+ <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
+ style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.sidebarWidth}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ annotationsKey={this.annotationKey}
+ isAnnotationOverlay={false}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+ </CollectionFreeFormView>
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} />
+ </div>}
+ {!this.layoutDoc._showAudio ? (null) :
+ <div className="formattedTextBox-dictation"
+ onPointerDown={e => {
+ runInAction(() => this._recording = !this._recording);
+ setTimeout(() => this._editorView!.focus(), 500);
+ e.stopPropagation();
+ }} >
+ <FontAwesomeIcon className="formattedTExtBox-audioFont"
+ style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+ </div>}
</div>
- {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
- <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
- <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
- style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- <CollectionFreeFormView {...this.props}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.sidebarWidth}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- annotationsKey={this.annotationKey}
- isAnnotationOverlay={false}
- focus={this.props.focus}
- isSelected={this.props.isSelected}
- select={emptyFunction}
- active={this.annotationsActive}
- ContentScaling={returnOne}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
- renderDepth={this.props.renderDepth + 1}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
- </CollectionFreeFormView>
- <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
- </div>}
- {!this.props.Document._showAudio ? (null) :
- <div className="formattedTextBox-dictation"
- onPointerDown={e => {
- runInAction(() => this._recording = !this._recording);
- setTimeout(() => this._editorView!.focus(), 500);
- e.stopPropagation();
- }} >
- <FontAwesomeIcon className="formattedTExtBox-audioFont"
- style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
- </div>}
</div>
);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index f9e4c5210..d47ae63af 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -2,9 +2,9 @@ import { Mark, ResolvedPos } from "prosemirror-model";
import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
-import { Doc, DocCastAsync } from "../../../../new_fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils";
+import { Doc, DocCastAsync } from "../../../../fields/Doc";
+import { Cast, FieldValue, NumCast } from "../../../../fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
import { schema } from "./schema_rts";
@@ -91,7 +91,7 @@ export class FormattedTextBoxComment {
(doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
}
} else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
- textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight");
+ textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight");
}
keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
@@ -192,21 +192,27 @@ export class FormattedTextBoxComment {
fitToBox={true}
moveDocument={returnFalse}
rootSelected={returnFalse}
- getTransform={Transform.Identity}
- active={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ parentActive={returnFalse}
addDocument={returnFalse}
removeDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
dontRegisterView={true}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
renderDepth={1}
PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
whenActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
/>, FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
- FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
+ FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%";
+ FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%";
}
// let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document ....
// let text = ext && StrCast(ext.text);
diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx
index 8f98da0fd..401ecd7e6 100644
--- a/src/client/views/nodes/formattedText/ImageResizeView.tsx
+++ b/src/client/views/nodes/formattedText/ImageResizeView.tsx
@@ -1,5 +1,5 @@
import { NodeSelection } from "prosemirror-state";
-import { Doc } from "../../../../new_fields/Doc";
+import { Doc } from "../../../../fields/Doc";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
import React = require("react");
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index a0b02880e..2f7d23021 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,10 +7,10 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "../../../util/SelectionManager";
import { Docs } from "../../../documents/Documents";
-import { NumCast, BoolCast, Cast, StrCast } from "../../../../new_fields/Types";
-import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
+import { Doc } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
-import { Id } from "../../../../new_fields/FieldSymbols";
+import { Id } from "../../../../fields/FieldSymbols";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 36da769c3..7a0718c16 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -54,6 +54,29 @@
input {
color: black;
}
+
+}
+
+.richTextMenu {
+ select {
+ background-color: #323232;
+ color: white;
+ border: 1px solid black;
+ // border-top: none;
+ // border-bottom: none;
+ font-size: 12px;
+ height: 100%;
+ margin-right: 3px;
+
+ &:focus,
+ &:hover {
+ background-color: black;
+ }
+
+ &::-ms-expand {
+ color: white;
+ }
+ }
}
.link-menu {
@@ -91,26 +114,6 @@
}
}
-select {
- background-color: #323232;
- color: white;
- border: 1px solid black;
- // border-top: none;
- // border-bottom: none;
- font-size: 12px;
- height: 100%;
- margin-right: 3px;
-
- &:focus,
- &:hover {
- background-color: black;
- }
-
- &::-ms-expand {
- color: white;
- }
-}
-
.row-2 {
display: flex;
justify-content: space-between;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cc04e0d6d..fd1b26208 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -11,14 +11,14 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
import { updateBullets } from "./ProsemirrorExampleTransfer";
import { FieldViewProps } from "../FieldView";
-import { Cast, StrCast } from "../../../../new_fields/Types";
+import { Cast, StrCast } from "../../../../fields/Types";
import { FormattedTextBoxProps } from "./FormattedTextBox";
import { unimplementedFunction, Utils } from "../../../../Utils";
import { wrapInList } from "prosemirror-schema-list";
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../new_fields/SchemaHeaderField';
+import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
import "./RichTextMenu.scss";
import { DocServer } from "../../../DocServer";
-import { Doc } from "../../../../new_fields/Doc";
+import { Doc } from "../../../../fields/Doc";
import { SelectionManager } from "../../../util/SelectionManager";
import { LinkManager } from "../../../util/LinkManager";
const { toggleMark, setBlockType } = require("prosemirror-commands");
@@ -197,9 +197,10 @@ export default class RichTextMenu extends AntimodeMenu {
} else {
toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
const { from, $from, to, empty } = tx.selection;
- if (!tx.doc.rangeHasMark(from, to, mark.type)) {
- toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
- } else dispatch(tx);
+ // if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+ // toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+ // } else
+ dispatch(tx);
});
}
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index d619bc4a0..fbd6c87bb 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,9 +1,9 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
import { NodeSelection, TextSelection } from "prosemirror-state";
-import { DataSym, Doc } from "../../../../new_fields/Doc";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { ComputedField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast } from "../../../../new_fields/Types";
+import { DataSym, Doc } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { Cast, NumCast } from "../../../../fields/Types";
import { returnFalse, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
@@ -11,6 +11,7 @@ import { FormattedTextBox } from "./FormattedTextBox";
import { wrappingInputRule } from "./prosemirrorPatches";
import RichTextMenu from "./RichTextMenu";
import { schema } from "./schema_rts";
+import { List } from "../../../../fields/List";
export class RichTextRules {
public Document: Doc;
@@ -64,11 +65,12 @@ export class RichTextRules {
// create an inline view of a tag stored under the '#' field
new InputRule(
- new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
+ new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
(state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- this.Document[DataSym]["#"] = tag;
+ const multiple = tag.split(";");
+ this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
return state.tr.deleteRange(start, end).insert(start, fieldView);
}),
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index cdb7374f8..91280dea4 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -8,14 +8,14 @@ import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-s
import { StepMap } from "prosemirror-transform";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { List } from "../../../../new_fields/List";
-import { ObjectField } from "../../../../new_fields/ObjectField";
-import { listSpec } from "../../../../new_fields/Schema";
-import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { List } from "../../../../fields/List";
+import { ObjectField } from "../../../../fields/ObjectField";
+import { listSpec } from "../../../../fields/Schema";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
@@ -217,6 +217,7 @@ export class DashDocView {
this._dashSpan.style.height = node.attrs.height;
this._dashSpan.style.position = "absolute";
this._dashSpan.style.display = "inline-block";
+ this._dashSpan.style.whiteSpace = "normal";
this._dashSpan.onpointerleave = () => {
const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
if (ele) {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 46bf481fb..ebaa23e99 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,6 @@
import React = require("react");
import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { Doc } from "../../../../new_fields/Doc";
+import { Doc } from "../../../../fields/Doc";
const emDOM: DOMOutputSpecArray = ["em", 0];
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index e7bcf444a..af39ef9c7 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -166,7 +166,8 @@ export const nodes: { [index: string]: NodeSpec } = {
inline: true,
attrs: {
fieldKey: { default: "" },
- docid: { default: "" }
+ docid: { default: "" },
+ hideKey: { default: false }
},
group: "inline",
draggable: false,
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 672d3adb8..cb6a15f36 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,10 +1,10 @@
import React = require("react");
import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
import { DocumentManager } from "../../util/DocumentManager";
import PDFMenu from "./PDFMenu";
import "./Annotation.scss";
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 2a6eff7ff..ff328068b 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { unimplementedFunction, returnFalse } from "../../../Utils";
import AntimodeMenu from "../AntimodeMenu";
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
@observer
export default class PDFMenu extends AntimodeMenu {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index acaa4363e..c50969493 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -4,16 +4,16 @@ import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
import * as rp from "request-promise";
import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { InkTool } from "../../../new_fields/InkField";
-import { List } from "../../../new_fields/List";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast } from "../../../new_fields/Types";
-import { PdfField } from "../../../new_fields/URLField";
-import { TraceMobx } from "../../../new_fields/util";
+import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
+import { InkTool } from "../../../fields/InkField";
+import { List } from "../../../fields/List";
+import { createSchema, makeInterface } from "../../../fields/Schema";
+import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, NumCast } from "../../../fields/Types";
+import { PdfField } from "../../../fields/URLField";
+import { TraceMobx } from "../../../fields/util";
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss
index 8370af490..ccd2e8947 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/presentationview/PresElementBox.scss
@@ -1,5 +1,4 @@
.presElementBox-item {
- padding: 10px;
display: inline-block;
background-color: #eeeeee;
pointer-events: all;
@@ -16,7 +15,6 @@
user-select: none;
transition: all .1s;
padding: 0px;
- padding-left: 5px;
padding-bottom: 3px;
.documentView-node {
position: absolute;
@@ -38,7 +36,7 @@
border-radius: 6px;
}
-.presElementBox-selected {
+.presElementBox-active {
background: gray;
color: black;
border-radius: 6px;
@@ -54,20 +52,27 @@
padding: 8px;
}
-.presElementBox-interaction {
- color: gray;
- float: left;
- padding: 0px;
- width: 20px;
- height: 20px;
-}
-.presElementBox-interaction-selected {
- color: white;
- float: left;
- padding: 0px;
- width: 22px;
- height: 22px;
+.presElementBox-buttons {
+ display: flow-root;
+ position: relative;
+ width: 100%;
+ height: auto;
+ .presElementBox-interaction {
+ color: gray;
+ float: left;
+ padding: 0px;
+ width: 20px;
+ height: 20px;
+ }
+ .presElementBox-interaction-selected {
+ color: white;
+ float: left;
+ padding: 0px;
+ width: 20px;
+ height: 20px;
+ border: solid 1px darkgray;
+ }
}
.presElementBox-name {
@@ -82,14 +87,10 @@
.presElementBox-embedded {
position: relative;
- margin-top: 22;
display: flex;
width: auto;
justify-content: center;
- .contentFittingDocumentView {
- position: absolute;
- height: 100%;
- }
+ margin:auto;
}
.presElementBox-embeddedMask {
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index dd0cbf929..280ba9093 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -1,15 +1,12 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
-import { faArrowDown, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DataSym } from "../../../new_fields/Doc";
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id } from "../../../new_fields/FieldSymbols";
-import { createSchema, makeInterface } from '../../../new_fields/Schema';
-import { Cast, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils";
+import { Doc, DataSym, DocListCast } from "../../../fields/Doc";
+import { documentSchema } from '../../../fields/documentSchemas';
+import { Id } from "../../../fields/FieldSymbols";
+import { createSchema, makeInterface } from '../../../fields/Schema';
+import { Cast, NumCast, BoolCast, ScriptCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -18,23 +15,16 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
import React = require("react");
-library.add(faArrowUp);
-library.add(fileSolid);
-library.add(faLocationArrow);
-library.add(fileRegular as any);
-library.add(faSearch);
-library.add(faArrowDown);
-
export const presSchema = createSchema({
presentationTargetDoc: Doc,
presBox: Doc,
- zoomButton: "boolean",
- navButton: "boolean",
- hideTillShownButton: "boolean",
- fadeButton: "boolean",
- hideAfterButton: "boolean",
- groupButton: "boolean",
- expandInlineButton: "boolean"
+ presZoomButton: "boolean",
+ presNavButton: "boolean",
+ presHideTillShownButton: "boolean",
+ presFadeButton: "boolean",
+ presHideAfterButton: "boolean",
+ presGroupButton: "boolean",
+ presExpandInlineButton: "boolean"
});
type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>;
@@ -48,16 +38,16 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
_heightDisposer: IReactionDisposer | undefined;
- @computed get indexInPres() { return NumCast(this.presElementDoc?.presentationIndex); }
- @computed get presBoxDoc() { return Cast(this.presElementDoc?.presBox, Doc) as Doc; }
- @computed get presElementDoc() { return this.rootDoc; }
- @computed get presLayoutDoc() { return this.layoutDoc; }
- @computed get targetDoc() { return this.presElementDoc?.presentationTargetDoc as Doc; }
- @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); }
+ // these fields are conditionally computed fields on the layout document that take this document as a parameter
+ @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+ @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation elemnt template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
+ @computed get presStatus() { return BoolCast(this.lookupField("presStatus")); }
+ @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
+ @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
componentDidMount() {
- this._heightDisposer = reaction(() => [this.presElementDoc.expandInlineButton, this.presElementDoc.collapsedHeight],
- params => this.presLayoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
+ params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
}
componentWillUnmount() {
this._heightDisposer?.();
@@ -70,13 +60,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
@action
onHideDocumentUntilPressClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.presElementDoc.hideTillShownButton = !this.presElementDoc.hideTillShownButton;
- if (!this.presElementDoc.hideTillShownButton) {
- if (this.indexInPres >= this.currentIndex && this.targetDoc) {
+ this.rootDoc.presHideTillShownButton = !this.rootDoc.presHideTillShownButton;
+ if (!this.rootDoc.presHideTillShownButton) {
+ if (this.indexInPres >= this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 1;
}
} else {
- if (this.presBoxDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) {
+ if (this.presStatus && this.indexInPres > this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 0;
}
}
@@ -90,14 +80,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
@action
onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.presElementDoc.hideAfterButton = !this.presElementDoc.hideAfterButton;
- if (!this.presElementDoc.hideAfterButton) {
- if (this.indexInPres <= this.currentIndex && this.targetDoc) {
+ this.rootDoc.presHideAfterButton = !this.rootDoc.presHideAfterButton;
+ if (!this.rootDoc.presHideAfterButton) {
+ if (this.indexInPres <= this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 1;
}
} else {
- if (this.presElementDoc.fadeButton) this.presElementDoc.fadeButton = false;
- if (this.presBoxDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) {
+ if (this.rootDoc.presFadeButton) this.rootDoc.presFadeButton = false;
+ if (this.presStatus && this.indexInPres < this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 0;
}
}
@@ -111,14 +101,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
@action
onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.presElementDoc.fadeButton = !this.presElementDoc.fadeButton;
- if (!this.presElementDoc.fadeButton) {
- if (this.indexInPres <= this.currentIndex && this.targetDoc) {
+ this.rootDoc.presFadeButton = !this.rootDoc.presFadeButton;
+ if (!this.rootDoc.presFadeButton) {
+ if (this.indexInPres <= this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 1;
}
} else {
- this.presElementDoc.hideAfterButton = false;
- if (this.presBoxDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) {
+ this.rootDoc.presHideAfterButton = false;
+ if (this.presStatus && (this.indexInPres < this.itemIndex) && this.targetDoc) {
this.targetDoc.opacity = 0.5;
}
}
@@ -130,11 +120,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
@action
onNavigateDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.presElementDoc.navButton = !this.presElementDoc.navButton;
- if (this.presElementDoc.navButton) {
- this.presElementDoc.zoomButton = false;
- if (this.currentIndex === this.indexInPres) {
- this.props.focus(this.presElementDoc);
+ this.rootDoc.presNavButton = !this.rootDoc.presNavButton;
+ if (this.rootDoc.presNavButton) {
+ this.rootDoc.presZoomButton = false;
+ if (this.itemIndex === this.indexInPres) {
+ this.props.focus(this.rootDoc);
}
}
}
@@ -146,13 +136,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
onZoomDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- this.presElementDoc.zoomButton = !this.presElementDoc.zoomButton;
- if (!this.presElementDoc.zoomButton) {
- this.presElementDoc.viewScale = 1;
- } else {
- this.presElementDoc.navButton = false;
- if (this.currentIndex === this.indexInPres) {
- this.props.focus(this.presElementDoc);
+ this.rootDoc.presZoomButton = !this.rootDoc.presZoomButton;
+ if (this.rootDoc.presZoomButton) {
+ this.rootDoc.presNavButton = false;
+ if (this.itemIndex === this.indexInPres) {
+ this.props.focus(this.rootDoc);
}
}
}
@@ -161,18 +149,18 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
*/
ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
- embedHeight = () => this.props.PanelHeight() - NumCast(this.presElementDoc.collapsedHeight);
+ embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
embedWidth = () => this.props.PanelWidth() - 20;
/**
* The function that is responsible for rendering the a preview or not for this
* presentation element.
*/
- renderEmbeddedInline = () => {
- return !this.presElementDoc.expandInlineButton || !this.targetDoc ? (null) :
- <div className="presElementBox-embedded" style={{ height: this.embedHeight() }}>
+ @computed get renderEmbeddedInline() {
+ return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
+ <div className="presElementBox-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
<ContentFittingDocumentView
Document={this.targetDoc}
- DataDocument={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+ DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
LibraryPath={emptyPath}
fitToBox={true}
rootSelected={returnTrue}
@@ -182,12 +170,18 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
pinToPres={returnFalse}
PanelWidth={this.embedWidth}
PanelHeight={this.embedHeight}
- getTransform={Transform.Identity}
- active={this.props.active}
+ ScreenToLocalTransform={Transform.Identity}
+ parentActive={this.props.active}
moveDocument={this.props.moveDocument!}
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
whenActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
/>
<div className="presElementBox-embeddedMask" />
</div>;
@@ -195,29 +189,29 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
render() {
const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree;
- const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : "");
+ const className = "presElementBox-item" + (this.itemIndex === this.indexInPres ? " presElementBox-active" : "");
const pbi = "presElementBox-interaction";
- return !(this.presElementDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : (
+ return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : (
<div className={className} key={this.props.Document[Id] + this.indexInPres}
style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }}
- onClick={e => { this.props.focus(this.presElementDoc); e.stopPropagation(); }}>
+ onClick={e => { this.props.focus(this.rootDoc); e.stopPropagation(); }}>
{treecontainer ? (null) : <>
<strong className="presElementBox-name">
{`${this.indexInPres + 1}. ${this.targetDoc?.title}`}
</strong>
- <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument?.(this.presElementDoc)}>X</button>
+ <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument?.(this.rootDoc)}>X</button>
<br />
</>}
- <button title="Zoom" className={pbi + (this.presElementDoc.zoomButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
- <button title="Navigate" className={pbi + (this.presElementDoc.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
- <button title="Hide Before" className={pbi + (this.presElementDoc.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
- <button title="Fade After" className={pbi + (this.presElementDoc.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
- <button title="Hide After" className={pbi + (this.presElementDoc.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
- <button title="Group With Up" className={pbi + (this.presElementDoc.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.groupButton = !this.presElementDoc.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button>
- <button title="Expand Inline" className={pbi + (this.presElementDoc.expandInlineButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.expandInlineButton = !this.presElementDoc.expandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} /></button>
-
- <br style={{ lineHeight: 0.1 }} />
- {this.renderEmbeddedInline()}
+ <div className="presElementBox-buttons">
+ <button title="Zoom" className={pbi + (this.rootDoc.presZoomButton ? "-selected" : "")} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Navigate" className={pbi + (this.rootDoc.presNavButton ? "-selected" : "")} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Hide Before" className={pbi + (this.rootDoc.presHideTillShownButton ? "-selected" : "")} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={"file"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Fade After" className={pbi + (this.rootDoc.presFadeButton ? "-selected" : "")} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Hide After" className={pbi + (this.rootDoc.presHideAfterButton ? "-selected" : "")} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Group With Up" className={pbi + (this.rootDoc.presGroupButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presGroupButton = !this.rootDoc.presGroupButton; }}><FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Expand Inline" className={pbi + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} onPointerDown={e => e.stopPropagation()} /></button>
+ </div>
+ {this.renderEmbeddedInline}
</div>
);
}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 662b37d77..4b53963a5 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -4,10 +4,10 @@ import { observable, action } from 'mobx';
import "./SearchBox.scss";
import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core';
-import { Doc } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/FieldSymbols';
+import { Doc } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
import { DocumentType } from "../../documents/DocumentTypes";
-import { Cast, StrCast } from '../../../new_fields/Types';
+import { Cast, StrCast } from '../../../fields/Types';
import * as _ from "lodash";
import { IconBar } from './IconBar';
import { FieldFilters } from './FieldFilters';
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index e41b725b1..eea7b528b 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -5,9 +5,9 @@ import { action, computed, observable, runInAction, IReactionDisposer, reaction
import { observer } from 'mobx-react';
import * as React from 'react';
import * as rp from 'request-promise';
-import { Doc } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { Doc } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
@@ -19,7 +19,7 @@ import { FieldView } from '../nodes/FieldView';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentView } from '../nodes/DocumentView';
import { SelectionManager } from '../../util/SelectionManager';
-import { listSpec } from '../../../new_fields/Schema';
+import { listSpec } from '../../../fields/Schema';
library.add(faTimes);
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index fe2000700..24d6e9d6f 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -4,10 +4,10 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils";
+import { Doc } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -164,14 +164,20 @@ export class SearchItem extends React.Component<SearchItemProps> {
removeDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
- getTransform={Transform.Identity}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ ScreenToLocalTransform={Transform.Identity}
renderDepth={1}
PanelWidth={returnXDimension}
PanelHeight={returnYDimension}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
focus={emptyFunction}
moveDocument={returnFalse}
- active={returnFalse}
+ parentActive={returnFalse}
whenActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
/>
</div>;
return docview;
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index fd6b47ff0..d541c8009 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -3,9 +3,9 @@ import * as ReactDOM from 'react-dom';
import { observer } from 'mobx-react';
import { observable, computed } from 'mobx';
import { CompileScript } from '../client/util/Scripting';
-import { makeInterface } from '../new_fields/Schema';
-import { ObjectField } from '../new_fields/ObjectField';
-import { RefField } from '../new_fields/RefField';
+import { makeInterface } from '../fields/Schema';
+import { ObjectField } from '../fields/ObjectField';
+import { RefField } from '../fields/RefField';
import { DocServer } from '../client/DocServer';
@observer
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 3baedce4b..17d3db8fd 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { DocServer } from '../client/DocServer';
-import { Doc } from '../new_fields/Doc';
+import { Doc } from '../fields/Doc';
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
import { Utils } from '../Utils';
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index a26d2e06a..6ce39d533 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,17 +3,17 @@ import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { observer } from 'mobx-react';
-import { Doc, Field, FieldResult, Opt } from '../new_fields/Doc';
+import { Doc, Field, FieldResult, Opt } from '../fields/Doc';
import { DocServer } from '../client/DocServer';
-import { Id } from '../new_fields/FieldSymbols';
-import { List } from '../new_fields/List';
-import { URLField } from '../new_fields/URLField';
+import { Id } from '../fields/FieldSymbols';
+import { List } from '../fields/List';
+import { URLField } from '../fields/URLField';
import { EditableView } from '../client/views/EditableView';
import { CompileScript } from '../client/util/Scripting';
-import { RichTextField } from '../new_fields/RichTextField';
-import { DateField } from '../new_fields/DateField';
-import { ScriptField } from '../new_fields/ScriptField';
-import CursorField from '../new_fields/CursorField';
+import { RichTextField } from '../fields/RichTextField';
+import { DateField } from '../fields/DateField';
+import { ScriptField } from '../fields/ScriptField';
+import CursorField from '../fields/CursorField';
DateField;
URLField;
diff --git a/src/new_fields/CursorField.ts b/src/fields/CursorField.ts
index 28467377b..28467377b 100644
--- a/src/new_fields/CursorField.ts
+++ b/src/fields/CursorField.ts
diff --git a/src/new_fields/DateField.ts b/src/fields/DateField.ts
index a925148c2..a925148c2 100644
--- a/src/new_fields/DateField.ts
+++ b/src/fields/DateField.ts
diff --git a/src/new_fields/Doc.ts b/src/fields/Doc.ts
index 153af933a..a1e1e11b1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -6,7 +6,7 @@ import { DocumentType } from "../client/documents/DocumentTypes";
import { Scripting, scriptingGlobal } from "../client/util/Scripting";
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
import { UndoManager } from "../client/util/UndoManager";
-import { intersectRect } from "../Utils";
+import { intersectRect, Utils } from "../Utils";
import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols";
import { List } from "./List";
import { ObjectField } from "./ObjectField";
@@ -19,6 +19,7 @@ import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from ".
import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
import { Docs, DocumentOptions } from "../client/documents/Documents";
import { PdfField, VideoField, AudioField, ImageField } from "./URLField";
+import { LinkManager } from "../client/util/LinkManager";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -183,7 +184,7 @@ export class Doc extends RefField {
let renderFieldKey: any;
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")];
if (typeof layoutField === "string") {
- renderFieldKey = layoutField.split("'")[1];
+ renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1];
} else {
return Cast(layoutField, Doc, null);
}
@@ -292,6 +293,9 @@ export namespace Doc {
export function IsPrototype(doc: Doc) {
return GetT(doc, "isPrototype", "boolean", true);
}
+ export function IsBaseProto(doc: Doc) {
+ return GetT(doc, "baseProto", "boolean", true);
+ }
export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
const hasProto = doc.proto instanceof Doc;
const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
@@ -463,6 +467,7 @@ export namespace Doc {
return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc;
}
+ 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
@@ -488,13 +493,16 @@ export namespace Doc {
const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]";
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
+
if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
expandedTemplateLayout = undefined;
+ _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
}
- else if (expandedTemplateLayout === undefined) {
- if (templateLayoutDoc.resolvedDataDoc === Doc.GetProto(targetDoc) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
+ else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
+ if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
+ _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true);
templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
setTimeout(action(() => {
if (!targetDoc[expandedLayoutFieldKey]) {
@@ -509,6 +517,7 @@ export namespace Doc {
if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List) {
dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
}
+ _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
}
}), 0);
}
@@ -581,32 +590,64 @@ export namespace Doc {
return copy;
}
- export function MakeClone(doc: Doc, cloneProto: boolean = true): Doc {
+ export function MakeClone(doc: Doc): Doc {
+ const cloneMap = new Map<string, Doc>();
+ const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
+ const copy = Doc.makeClone(doc, cloneMap, rtfMap);
+ rtfMap.map(({ copy, key, field }) => {
+ const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
+ const mapped = cloneMap.get(id);
+ return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
+ };
+ const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
+ const mapped = cloneMap.get(id);
+ return href + (mapped ? mapped[Id] : id);
+ };
+ const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
+ const re = new RegExp(regex, "g");
+ copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ });
+ return copy;
+ }
+
+ export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc {
+ if (Doc.IsBaseProto(doc)) return doc;
+ if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
const copy = new Doc(undefined, true);
+ cloneMap.set(doc[Id], copy);
+ if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
const exclude = Cast(doc.excludeFields, listSpec("string"), []);
Object.keys(doc).forEach(key => {
if (exclude.includes(key)) return;
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && cloneProto) {
+ const copyObjectField = (field: ObjectField) => {
+ const list = Cast(doc[key], listSpec(Doc));
+ if (list !== undefined && !(list instanceof Promise)) {
+ copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs)));
+ } else if (doc[key] instanceof Doc) {
+ copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields
+ } else {
+ copy[key] = ObjectField.MakeCopy(field);
+ if (field instanceof RichTextField) {
+ if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
+ rtfs.push({ copy, key, field });
+ }
+ }
+ }
+ };
+ if (key === "proto") {
if (doc[key] instanceof Doc) {
- copy[key] = Doc.MakeClone(doc[key]!, false);
+ copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs);
}
} else {
if (field instanceof RefField) {
copy[key] = field;
} else if (cfield instanceof ComputedField) {
copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
+ (key === "links" && field instanceof ObjectField) && copyObjectField(field);
} else if (field instanceof ObjectField) {
- const list = Cast(doc[key], listSpec(Doc));
- if (list !== undefined && !(list instanceof Promise)) {
- copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.MakeCopy(d as Doc, false)));
- } else {
- copy[key] = doc[key] instanceof Doc ?
- key.includes("layout[") ?
- Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields
- ObjectField.MakeCopy(field);
- }
+ copyObjectField(field);
} else if (field instanceof Promise) {
debugger; //This shouldn't happend...
} else {
@@ -616,6 +657,7 @@ export namespace Doc {
});
Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
copy.cloneOf = doc;
+ cloneMap.set(doc[Id], copy);
return copy;
}
@@ -772,7 +814,7 @@ export namespace Doc {
export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; }
- export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layout_key1" : "layout_key2"; }
+ export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; }
export function linkFollowUnhighlight() {
Doc.UnhighlightAll();
@@ -893,19 +935,28 @@ export namespace Doc {
}
}
}
-
- export function freezeNativeDimensions(layoutDoc: Doc, width: number, height: number): void {
- layoutDoc._autoHeight = false;
- if (!layoutDoc._nativeWidth) {
- layoutDoc._nativeWidth = NumCast(layoutDoc._width, width);
- layoutDoc._nativeHeight = NumCast(layoutDoc._height, height);
- }
- }
export function assignDocToField(doc: Doc, field: string, id: string) {
DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
return id;
}
+ export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) {
+ runInAction(() => {
+ if (layoutDoc._nativeWidth || layoutDoc._nativeHeight) {
+ layoutDoc.scale = NumCast(layoutDoc.scale, 1) * contentScale;
+ layoutDoc._nativeWidth = undefined;
+ layoutDoc._nativeHeight = undefined;
+ }
+ else {
+ layoutDoc._autoHeight = false;
+ if (!layoutDoc._nativeWidth) {
+ layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
+ layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight);
+ }
+ }
+ });
+ }
+
export function isDocPinned(doc: Doc) {
//add this new doc to props.Document
const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
@@ -997,7 +1048,7 @@ export namespace Doc {
//newCollection.borderRounding = "40px";
newCollection._jitterRotation = 10;
newCollection._backgroundColor = "gray";
- newCollection.overflow = "visible";
+ newCollection._overflow = "visible";
return newCollection;
}
diff --git a/src/new_fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts
index 8d040f493..8d040f493 100644
--- a/src/new_fields/FieldSymbols.ts
+++ b/src/fields/FieldSymbols.ts
diff --git a/src/new_fields/HtmlField.ts b/src/fields/HtmlField.ts
index 6e8bba977..6e8bba977 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/fields/HtmlField.ts
diff --git a/src/new_fields/IconField.ts b/src/fields/IconField.ts
index 76c4ddf1b..76c4ddf1b 100644
--- a/src/new_fields/IconField.ts
+++ b/src/fields/IconField.ts
diff --git a/src/new_fields/InkField.ts b/src/fields/InkField.ts
index bb93de5ac..bb93de5ac 100644
--- a/src/new_fields/InkField.ts
+++ b/src/fields/InkField.ts
diff --git a/src/new_fields/List.ts b/src/fields/List.ts
index a43f11e82..fdabea365 100644
--- a/src/new_fields/List.ts
+++ b/src/fields/List.ts
@@ -2,12 +2,13 @@ import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/
import { Field } from "./Doc";
import { setter, getter, deleteProperty, updateFunction } from "./util";
import { serializable, alias, list } from "serializr";
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
import { ObjectField } from "./ObjectField";
import { RefField } from "./RefField";
import { ProxyField } from "./Proxy";
-import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy } from "./FieldSymbols";
+import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy, Id } from "./FieldSymbols";
import { Scripting } from "../client/util/Scripting";
+import { DocServer } from "../client/DocServer";
const listHandlers: any = {
/// Mutator methods
@@ -54,11 +55,13 @@ const listHandlers: any = {
return res;
},
sort(cmpFunc: any) {
+ this[Self].__realFields(); // coerce retrieving entire array
const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
this[Update]();
return res;
},
splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ this[Self].__realFields(); // coerce retrieving entire array
items = items.map(toObjectField);
const list = this[Self];
for (let i = 0; i < items.length; i++) {
@@ -94,102 +97,102 @@ const listHandlers: any = {
},
/// Accessor methods
concat: action(function (this: any, ...items: any[]) {
+ this[Self].__realFields();
return this[Self].__fields.map(toRealField).concat(...items);
}),
includes(valueToFind: any, fromIndex: number) {
- const fields = this[Self].__fields;
if (valueToFind instanceof RefField) {
- return fields.map(toRealField).includes(valueToFind, fromIndex);
+ return this[Self].__realFields().includes(valueToFind, fromIndex);
} else {
- return fields.includes(valueToFind, fromIndex);
+ return this[Self].__fields.includes(valueToFind, fromIndex);
}
},
indexOf(valueToFind: any, fromIndex: number) {
- const fields = this[Self].__fields;
if (valueToFind instanceof RefField) {
- return fields.map(toRealField).indexOf(valueToFind, fromIndex);
+ return this[Self].__realFields().indexOf(valueToFind, fromIndex);
} else {
- return fields.indexOf(valueToFind, fromIndex);
+ return this[Self].__fields.indexOf(valueToFind, fromIndex);
}
},
join(separator: any) {
+ this[Self].__realFields();
return this[Self].__fields.map(toRealField).join(separator);
},
lastIndexOf(valueToFind: any, fromIndex: number) {
- const fields = this[Self].__fields;
if (valueToFind instanceof RefField) {
- return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex);
+ return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
} else {
- return fields.lastIndexOf(valueToFind, fromIndex);
+ return this[Self].__fields.lastIndexOf(valueToFind, fromIndex);
}
},
slice(begin: number, end: number) {
+ this[Self].__realFields();
return this[Self].__fields.slice(begin, end).map(toRealField);
},
/// Iteration methods
entries() {
- return this[Self].__fields.map(toRealField).entries();
+ return this[Self].__realFields().entries();
},
every(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).every(callback, thisArg);
+ return this[Self].__realFields().every(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
filter(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).filter(callback, thisArg);
+ return this[Self].__realFields().filter(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
find(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).find(callback, thisArg);
+ return this[Self].__realFields().find(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
findIndex(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).findIndex(callback, thisArg);
+ return this[Self].__realFields().findIndex(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
forEach(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).forEach(callback, thisArg);
+ return this[Self].__realFields().forEach(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
map(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).map(callback, thisArg);
+ return this[Self].__realFields().map(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
reduce(callback: any, initialValue: any) {
- return this[Self].__fields.map(toRealField).reduce(callback, initialValue);
+ return this[Self].__realFields().reduce(callback, initialValue);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
},
reduceRight(callback: any, initialValue: any) {
- return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue);
+ return this[Self].__realFields().reduceRight(callback, initialValue);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
},
some(callback: any, thisArg: any) {
- return this[Self].__fields.map(toRealField).some(callback, thisArg);
+ return this[Self].__realFields().some(callback, thisArg);
// TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
// If we don't want to support the array parameter, we should use this version instead
// return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
},
values() {
- return this[Self].__fields.map(toRealField).values();
+ return this[Self].__realFields().values();
},
[Symbol.iterator]() {
- return this[Self].__fields.map(toRealField).values();
+ return this[Self].__realFields().values();
}
};
@@ -254,6 +257,31 @@ class ListImpl<T extends Field> extends ObjectField {
[key: number]: T | (T extends RefField ? Promise<T> : never);
+ // 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() : "");
+ // 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);
+ // 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]]));
+ }));
+ // 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
+ // handler for 'promise' in the lines below.
+ waiting.map((w, i) => {
+ w instanceof ProxyField && w.setPromise(promise.then(fields => fields[promised[i]]));
+ });
+ }
+ return this.__fields.map(toRealField);
+ }
+
@serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
@@ -283,7 +311,7 @@ class ListImpl<T extends Field> extends ObjectField {
// console.log(diff);
const update = this[OnUpdate];
// update && update(diff);
- update && update();
+ update?.();
}
private [Self] = this;
diff --git a/src/new_fields/ListSpec.ts b/src/fields/ListSpec.ts
index e69de29bb..e69de29bb 100644
--- a/src/new_fields/ListSpec.ts
+++ b/src/fields/ListSpec.ts
diff --git a/src/new_fields/ObjectField.ts b/src/fields/ObjectField.ts
index 9aa1c9b04..9aa1c9b04 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
diff --git a/src/new_fields/PresField.ts b/src/fields/PresField.ts
index f236a04fd..f236a04fd 100644
--- a/src/new_fields/PresField.ts
+++ b/src/fields/PresField.ts
diff --git a/src/new_fields/Proxy.ts b/src/fields/Proxy.ts
index d50c0f14e..555faaad0 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,7 +1,7 @@
import { Deserializable } from "../client/util/SerializationHelper";
import { FieldWaiting } from "./Doc";
import { primitive, serializable } from "serializr";
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
import { DocServer } from "../client/DocServer";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
@@ -61,6 +61,11 @@ export class ProxyField<T extends RefField> extends ObjectField {
return undefined;
}
if (!this.promise) {
+ const cached = DocServer.GetCachedRefField(this.fieldId);
+ if (cached !== undefined) {
+ runInAction(() => this.cache = cached as any);
+ return cached as any;
+ }
this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
this.promise = undefined;
this.cache = field;
@@ -70,6 +75,17 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
return this.promise as any;
}
+ promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; }
+ setPromise(promise: any) {
+ this.promise = promise;
+ }
+ @action
+ setValue(field: any) {
+ this.promise = undefined;
+ this.cache = field;
+ if (field === undefined) this.failed = true;
+ return field;
+ }
}
export namespace ProxyField {
diff --git a/src/new_fields/RefField.ts b/src/fields/RefField.ts
index b6ef69750..b6ef69750 100644
--- a/src/new_fields/RefField.ts
+++ b/src/fields/RefField.ts
diff --git a/src/new_fields/RichTextField.ts b/src/fields/RichTextField.ts
index 5cf0e0cc3..5cf0e0cc3 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
diff --git a/src/new_fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index c475d0d73..c475d0d73 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
diff --git a/src/new_fields/Schema.ts b/src/fields/Schema.ts
index 72bce283d..72bce283d 100644
--- a/src/new_fields/Schema.ts
+++ b/src/fields/Schema.ts
diff --git a/src/new_fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 07c90f5a2..07c90f5a2 100644
--- a/src/new_fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
diff --git a/src/new_fields/ScriptField.ts b/src/fields/ScriptField.ts
index 8d0ddf94c..f05f431ac 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,10 +1,10 @@
import { ObjectField } from "./ObjectField";
-import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions } from "../client/util/Scripting";
+import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions, CompileError, CompileResult } from "../client/util/Scripting";
import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols";
import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Doc, Field } from "../new_fields/Doc";
-import { Plugins } from "./util";
+import { Doc, Field } from "./Doc";
+import { Plugins, setter } from "./util";
import { computedFn } from "mobx-utils";
import { ProxyField } from "./Proxy";
import { Cast } from "./Types";
@@ -59,18 +59,20 @@ async function deserializeScript(script: ScriptField) {
export class ScriptField extends ObjectField {
@serializable(object(scriptSchema))
readonly script: CompiledScript;
+ @serializable(object(scriptSchema))
+ readonly setterscript: CompiledScript | undefined;
@serializable(autoObject())
private captures?: ProxyField<Doc>;
- constructor(script: CompiledScript) {
+ constructor(script: CompiledScript, setterscript?: CompileResult) {
super();
- if (script && script.options.capturedVariables) {
+ if (script?.options.capturedVariables) {
const doc = Doc.assign(new Doc, script.options.capturedVariables);
this.captures = new ProxyField(doc);
}
-
+ this.setterscript = setterscript?.compiled ? setterscript : undefined;
this.script = script;
}
@@ -136,13 +138,27 @@ export class ComputedField extends ScriptField {
//TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
_valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result;
+
+
+ constructor(script: CompiledScript, setterscript?: CompiledScript) {
+ super(script,
+ !setterscript && script?.originalScript.includes("self.timecode") ?
+ ScriptField.CompileScript("self['x' + self.timecode] = value", { value: "any" }, true) : setterscript);
+ }
+
public static MakeScript(script: string, params: object = {}) {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }, setterScript?: string) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
- return compiled.compiled ? new ComputedField(compiled) : undefined;
+ const setCompiled = setterScript ? ScriptField.CompileScript(setterScript, params, true, capturedVariables) : undefined;
+ return compiled.compiled ? new ComputedField(compiled, setCompiled?.compiled ? setCompiled : undefined) : undefined;
+ }
+ public static MakeInterpolated(fieldKey: string, interpolatorKey: string) {
+ const getField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}]`, {}, true, {});
+ const setField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}] = value`, { value: "any" }, true, {});
+ return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
}
diff --git a/src/new_fields/Types.ts b/src/fields/Types.ts
index aa44cefa0..3d784448d 100644
--- a/src/new_fields/Types.ts
+++ b/src/fields/Types.ts
@@ -88,8 +88,8 @@ export function DateCast(field: FieldResult) {
return Cast(field, DateField, null);
}
-export function ScriptCast(field: FieldResult) {
- return Cast(field, ScriptField, null);
+export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) {
+ return Cast(field, ScriptField, defaultVal);
}
type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
diff --git a/src/new_fields/URLField.ts b/src/fields/URLField.ts
index fb71160ca..fb71160ca 100644
--- a/src/new_fields/URLField.ts
+++ b/src/fields/URLField.ts
diff --git a/src/new_fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 7a0be8863..cacba43b6 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -4,13 +4,25 @@ import { Doc } from "./Doc";
import { DateField } from "./DateField";
export const documentSchema = createSchema({
+ // content properties
type: "string", // enumerated type of document -- should be template-specific (ie, start with an '_')
- layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
- layoutKey: "string", // holds the field key for the field that actually holds the current lyoat
title: "string", // document title (can be on either data document or layout)
- dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
- targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
- childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+ isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field
+ creationDate: DateField, // when the document was created
+ links: listSpec(Doc), // computed (readonly) list of links associated with this document
+
+ // "Location" properties in a very general sense
+ currentTimecode: "number", // current play back time of a temporal document (video / audio)
+ displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+ inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
+ x: "number", // x coordinate when in a freeform view
+ y: "number", // y coordinate when in a freeform view
+ z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview
+ zIndex: "number", // zIndex of a document in a freeform view
+ scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+ scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
+
+ // appearance properties on the layout
_autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents
_nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set
_nativeHeight: "number", // "
@@ -20,72 +32,69 @@ export const documentSchema = createSchema({
_yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set
_xMargin: "number", // margin added on left/right of most documents to add separation from their container
_yMargin: "number", // margin added on top/bottom of most documents to add separation from their container
+ _overflow: "string", // sets overflow behvavior for CollectionFreeForm views
_showCaption: "string", // whether editable caption text is overlayed at the bottom of the document
_showTitle: "string", // the fieldkey whose contents should be displayed at the top of the document
_showTitleHover: "string", // the showTitle should be shown only on hover
_showAudio: "boolean", // whether to show the audio record icon on documents
_freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
_LODdisable: "boolean", // whether to disbale LOD switching for CollectionFreeFormViews
- _pivotField: "string", // specifies which field should be used as the timeline/pivot axis
+ _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis
_replacedChrome: "string", // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
_chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
- _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
_fontSize: "number",
_fontFamily: "string",
- isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
- color: "string", // foreground color of document
+ _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
+
+ // appearance properties on the data document
backgroundColor: "string", // background color of document
+ borderRounding: "string", // border radius rounding of document
+ boxShadow: "string", // the amount of shadow around the perimeter of a document
+ color: "string", // foreground color of document
+ fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view
+ fontSize: "string",
+ layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
+ layoutKey: "string", // holds the field key for the field that actually holds the current lyoat
+ letterSpacing: "string",
opacity: "number", // opacity of document
- overflow: "string", // sets overflow behvavior for CollectionFreeForm views
- creationDate: DateField, // when the document was created
- links: listSpec(Doc), // computed (readonly) list of links associated with this document
+ strokeWidth: "number",
+ textTransform: "string",
+ treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden
+ treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
+ treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+
+ // interaction and linking properties
+ ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events)
onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped.
- dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
- removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
- isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field
- isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee)
- treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden
- treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
- treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
- currentTimecode: "number", // current play back time of a temporal document (video / audio)
followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, )
+ isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
+ isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked
+ isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee)
lockedPosition: "boolean", // whether the document can be moved (dragged)
lockedTransform: "boolean", // whether the document can be panned/zoomed
- inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
- borderRounding: "string", // border radius rounding of document
- heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
- isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked
- ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events)
- scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
- scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
- scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
- strokeWidth: "number",
- fontSize: "string",
- fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view
- letterSpacing: "string",
- textTransform: "string",
- childTemplateName: "string" // the name of a template to use to override the layoutKey when rendering a document in DocHolderBox
-});
-export const positionSchema = createSchema({
- zIndex: "number",
- x: "number",
- y: "number",
- z: "number",
+ // drag drop properties
+ dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
+ dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+ targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
+ childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+ removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
});
+
export const collectionSchema = createSchema({
- childLayout: Doc, // layout template for children of a collecion
- childDetailView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field)
+ childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox
+ childLayoutTemplate: Doc, // layout template to use to render children of a collecion
+ childLayoutString: "string", //layout string to use to render children of a collection
+ childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
+ dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
onChildClick: ScriptField, // script to run for each child when its clicked
+ onChildDoubleClick: ScriptField, // script to run for each child when its clicked
onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
});
export type Document = makeInterface<[typeof documentSchema]>;
export const Document = makeInterface(documentSchema);
-
-export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-export const PositionDocument = makeInterface(documentSchema, positionSchema);
diff --git a/src/new_fields/util.ts b/src/fields/util.ts
index 8c719ccd8..a287b0210 100644
--- a/src/new_fields/util.ts
+++ b/src/fields/util.ts
@@ -7,6 +7,8 @@ import { ObjectField } from "./ObjectField";
import { action, trace } from "mobx";
import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
+import { ComputedField } from "./ScriptField";
+import { ScriptCast } from "./Types";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -114,6 +116,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
return true;
}
}
+ if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript) {
+ return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
+ }
return _setter(target, prop, value, receiver);
}
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 295e82142..231870531 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -4,16 +4,15 @@ import { Docs } from '../client/documents/Documents';
import "./ImageUpload.scss";
import React = require('react');
import { DocServer } from '../client/DocServer';
-import { Opt, Doc } from '../new_fields/Doc';
-import { Cast } from '../new_fields/Types';
-import { listSpec } from '../new_fields/Schema';
-import { List } from '../new_fields/List';
+import { Opt, Doc } from '../fields/Doc';
+import { Cast } from '../fields/Types';
+import { listSpec } from '../fields/Schema';
+import { List } from '../fields/List';
import { observer } from 'mobx-react';
import { observable } from 'mobx';
import { Utils } from '../Utils';
import MobileInterface from './MobileInterface';
-import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
-import { Scripting } from '../client/util/Scripting';
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 1537ae034..973931615 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -4,11 +4,11 @@ import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition
import { observable, action } from "mobx";
import { GestureUtils } from "../pen-gestures/GestureUtils";
import "./MobileInkOverlay.scss";
-import { StrCast, Cast } from '../new_fields/Types';
+import { StrCast, Cast } from '../fields/Types';
import { DragManager } from "../client/util/DragManager";
import { DocServer } from '../client/DocServer';
-import { Doc, DocListCastAsync } from '../new_fields/Doc';
-import { listSpec } from '../new_fields/Schema';
+import { Doc, DocListCastAsync } from '../fields/Doc';
+import { listSpec } from '../fields/Schema';
@observer
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 69a80e1b4..6c2e797d6 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,22 +10,22 @@ import { DocumentManager } from '../client/util/DocumentManager';
import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
import { Scripting } from '../client/util/Scripting';
import { Transform } from '../client/util/Transform';
-import { CollectionView } from '../client/views/collections/CollectionView';
import { DocumentDecorations } from '../client/views/DocumentDecorations';
import GestureOverlay from '../client/views/GestureOverlay';
import { InkingControl } from '../client/views/InkingControl';
import { DocumentView } from '../client/views/nodes/DocumentView';
import { RadialMenu } from '../client/views/nodes/RadialMenu';
import { PreviewCursor } from '../client/views/PreviewCursor';
-import { Doc, DocListCast, FieldResult } from '../new_fields/Doc';
-import { Id } from '../new_fields/FieldSymbols';
-import { InkTool } from '../new_fields/InkField';
-import { listSpec } from '../new_fields/Schema';
-import { Cast, FieldValue } from '../new_fields/Types';
-import { WebField } from "../new_fields/URLField";
-import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
+import { Doc, DocListCast, FieldResult } from '../fields/Doc';
+import { Id } from '../fields/FieldSymbols';
+import { InkTool } from '../fields/InkField';
+import { listSpec } from '../fields/Schema';
+import { Cast, FieldValue } from '../fields/Types';
+import { WebField } from "../fields/URLField";
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
import "./MobileInterface.scss";
+import { CollectionView } from '../client/views/collections/CollectionView';
library.add(faLongArrowAltLeft);
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index b8a82ab4d..3b6170f68 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,9 +1,9 @@
import { NDollarRecognizer } from "./ndollar";
import { Type } from "typescript";
-import { InkField, PointData } from "../new_fields/InkField";
+import { InkField, PointData } from "../fields/InkField";
import { Docs } from "../client/documents/Documents";
-import { Doc, WidthSym, HeightSym } from "../new_fields/Doc";
-import { NumCast } from "../new_fields/Types";
+import { Doc, WidthSym, HeightSym } from "../fields/Doc";
+import { NumCast } from "../fields/Types";
import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
import { Rect } from "react-measure";
import { Scripting } from "../client/util/Scripting";
diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts
index 713207a07..e55850b29 100644
--- a/src/scraping/buxton/final/BuxtonImporter.ts
+++ b/src/scraping/buxton/final/BuxtonImporter.ts
@@ -10,6 +10,10 @@ import { parseXml } from "libxmljs";
import { strictEqual } from "assert";
import { Readable, PassThrough } from "stream";
+/**
+ * This is an arbitrary bundle of data that gets populated
+ * in extractFileContents
+ */
interface DocumentContents {
body: string;
imageData: ImageData[];
@@ -19,6 +23,10 @@ interface DocumentContents {
longDescription: string;
}
+/**
+ * A rough schema for everything that Bill has
+ * included for each document
+ */
export interface DeviceDocument {
title: string;
shortDescription: string;
@@ -33,36 +41,65 @@ export interface DeviceDocument {
attribute: string;
__images: ImageData[];
hyperlinks: string[];
- captions: string[];
- embeddedFileNames: string[];
+ captions: string[]; // from the table column
+ embeddedFileNames: string[]; // from the table column
}
+/**
+ * A layer of abstraction around a single parsing
+ * attempt. The error is not a TypeScript error, but
+ * rather an invalidly formatted value for a given key.
+ */
export interface AnalysisResult {
device?: DeviceDocument;
- errors?: { [key: string]: string };
+ invalid?: { [deviceProperty: string]: string };
}
+/**
+ * A mini API that takes in a string and returns
+ * either the given T or an error indicating that the
+ * transformation was rejected.
+ */
type Transformer<T> = (raw: string) => TransformResult<T>;
interface TransformResult<T> {
transformed?: T;
error?: string;
}
+/**
+ * Simple bundle counting successful and failed imports
+ */
export interface ImportResults {
deviceCount: number;
errorCount: number;
}
+/**
+ * Definitions for callback functions. Such instances are
+ * just invoked by when a single document has been parsed
+ * or the entire import is over. As of this writing, these
+ * callbacks are supplied by WebSocket.ts and used to inform
+ * the client of these events.
+ */
type ResultCallback = (result: AnalysisResult) => void;
type TerminatorCallback = (result: ImportResults) => void;
-interface Processor<T> {
- exp: RegExp;
- matchIndex?: number;
- transformer?: Transformer<T>;
- required?: boolean;
+/**
+ * Defines everything needed to define how a single key should be
+ * formatted within the plain body text. The association between
+ * keys and their format definitions is stored FormatMap
+ */
+interface ValueFormatDefinition<T> {
+ exp: RegExp; // the expression that the key's value should match
+ matchIndex?: number; // defaults to 0, but can be overridden to account for grouping in @param exp
+ transformer?: Transformer<T>; // if desirable, how to transform the Regex match
+ required?: boolean; // defaults to true, confirms that for a whole document to be counted successful,
+ // all of its required values should be present and properly formatted
}
+/**
+ * The basic data we extract from each image in the document
+ */
interface ImageData {
url: string;
nativeWidth: number;
@@ -71,6 +108,10 @@ interface ImageData {
namespace Utilities {
+ /**
+ * Numeric 'try parse', fits with the Transformer API
+ * @param raw the serialized number
+ */
export function numberValue(raw: string): TransformResult<number> {
const transformed = Number(raw);
if (isNaN(transformed)) {
@@ -79,18 +120,32 @@ namespace Utilities {
return { transformed };
}
+ /**
+ * A simple tokenizer that splits along 'and' and commas, and removes duplicates
+ * Helpful mainly for attribute and primary key lists
+ * @param raw the string to tokenize
+ */
export function collectUniqueTokens(raw: string): TransformResult<string[]> {
const pieces = raw.replace(/,|\s+and\s+/g, " ").split(/\s+/).filter(piece => piece.length);
const unique = new Set(pieces.map(token => token.toLowerCase().trim()));
return { transformed: Array.from(unique).map(capitalize).sort() };
}
+ /**
+ * Tries to correct XML text parsing artifact where some sentences lose their separating space,
+ * and others gain excess whitespace
+ * @param raw
+ */
export function correctSentences(raw: string): TransformResult<string> {
raw = raw.replace(/\./g, ". ").replace(/\:/g, ": ").replace(/\,/g, ", ").replace(/\?/g, "? ").trimRight();
raw = raw.replace(/\s{2,}/g, " ");
return { transformed: raw };
}
+ /**
+ * Simple capitalization
+ * @param word to capitalize
+ */
export function capitalize(word: string): string {
const clean = word.trim();
if (!clean.length) {
@@ -99,6 +154,12 @@ namespace Utilities {
return word.charAt(0).toUpperCase() + word.slice(1);
}
+ /**
+ * Streams the requeted file at the relative path to the
+ * root of the zip, then parses it with a library
+ * @param zip the zip instance data source
+ * @param relativePath the path to a .xml file within the zip to parse
+ */
export async function readAndParseXml(zip: any, relativePath: string) {
console.log(`Text streaming ${relativePath}`);
const contents = await new Promise<string>((resolve, reject) => {
@@ -111,13 +172,17 @@ namespace Utilities {
stream.on('end', () => resolve(body));
});
});
-
return parseXml(contents);
}
-
}
-const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
+/**
+ * Defines how device values should be formatted. As you can see, the formatting is
+ * not super consistent and has changed over time as edge cases have been found, but this
+ * at least imposes some constraints, and will notify you if a document doesn't match the specifications
+ * in this map.
+ */
+const FormatMap = new Map<keyof DeviceDocument, ValueFormatDefinition<any>>([
["title", {
exp: /contact\s+(.*)Short Description:/
}],
@@ -189,17 +254,25 @@ const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
}],
]);
-const sourceDir = path.resolve(__dirname, "source");
-const outDir = path.resolve(__dirname, "json");
-const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton");
-const successOut = "buxton.json";
-const failOut = "incomplete.json";
-const deviceKeys = Array.from(RegexMap.keys());
-
+const sourceDir = path.resolve(__dirname, "source"); // where the Word documents are assumed to be stored
+const outDir = path.resolve(__dirname, "json"); // where the JSON output of these device documents will be written
+const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton"); // where, in the server, these images will be written
+const successOut = "buxton.json"; // the JSON list representing properly formatted documents
+const failOut = "incomplete.json"; // the JSON list representing improperly formatted documents
+const deviceKeys = Array.from(FormatMap.keys()); // a way to iterate through all keys of the DeviceDocument interface
+
+/**
+ * Starts by REMOVING ALL EXISTING BUXTON RESOURCES. This might need to be
+ * changed going forward
+ * @param emitter the callback when each document is completed
+ * @param terminator the callback when the entire import is completed
+ */
export default async function executeImport(emitter: ResultCallback, terminator: TerminatorCallback) {
try {
+ // get all Word documents in the source directory
const contents = readdirSync(sourceDir);
const wordDocuments = contents.filter(file => /.*\.docx?$/.test(file)).map(file => `${sourceDir}/${file}`);
+ // removal takes place here
[outDir, imageDir].forEach(dir => {
rimraf.sync(dir);
mkdirSync(dir);
@@ -216,19 +289,28 @@ export default async function executeImport(emitter: ResultCallback, terminator:
}
}
+/**
+ * Parse every Word document in the directory, notifying any callers as needed
+ * at each iteration via the emitter.
+ * @param wordDocuments the string list of Word document names to parse
+ * @param emitter the callback when each document is completed
+ * @param terminator the callback when the entire import is completed
+ */
async function parseFiles(wordDocuments: string[], emitter: ResultCallback, terminator: TerminatorCallback): Promise<DeviceDocument[]> {
+ // execute parent-most parse function
const results: AnalysisResult[] = [];
for (const filePath of wordDocuments) {
- const fileName = path.basename(filePath).replace("Bill_Notes_", "");
+ const fileName = path.basename(filePath).replace("Bill_Notes_", ""); // not strictly needed, but cleaner
console.log(cyan(`\nExtracting contents from ${fileName}...`));
const result = analyze(fileName, await extractFileContents(filePath));
emitter(result);
results.push(result);
}
+ // collect information about errors and successes
const masterDevices: DeviceDocument[] = [];
const masterErrors: { [key: string]: string }[] = [];
- results.forEach(({ device, errors }) => {
+ results.forEach(({ device, invalid: errors }) => {
if (device) {
masterDevices.push(device);
} else if (errors) {
@@ -236,24 +318,48 @@ async function parseFiles(wordDocuments: string[], emitter: ResultCallback, term
}
});
+ // something went wrong, since errors and successes should sum to total inputs
const total = wordDocuments.length;
if (masterDevices.length + masterErrors.length !== total) {
throw new Error(`Encountered a ${masterDevices.length} to ${masterErrors.length} mismatch in device / error split!`);
}
+ // write the external JSON representations of this import
console.log();
await writeOutputFile(successOut, masterDevices, total, true);
await writeOutputFile(failOut, masterErrors, total, false);
console.log();
+ // notify the caller that the import has finished
terminator({ deviceCount: masterDevices.length, errorCount: masterErrors.length });
return masterDevices;
}
-const tableCellXPath = '//*[name()="w:tbl"]/*[name()="w:tr"]/*[name()="w:tc"]';
-const hyperlinkXPath = '//*[name()="Relationship" and contains(@Type, "hyperlink")]';
-
+/**
+ * XPath definitions for desired XML targets in respective hierarchies.
+ *
+ * For table cells, can be read as: "find me anything that looks like <w:tc> in XML, whose
+ * parent looks like <w:tr>, whose parent looks like <w:tbl>"
+ *
+ * <w:tbl>
+ * <w:tr>
+ * <w:tc>
+ *
+ * These are found by trial and error, and using an online XML parser / prettifier
+ * to inspect the structure, since the Node XML library does not expose the parsed
+ * structure very well for searching, say in the debug console.
+ */
+const xPaths = {
+ paragraphs: '//*[name()="w:p"]',
+ tableCells: '//*[name()="w:tbl"]/*[name()="w:tr"]/*[name()="w:tc"]',
+ hyperlinks: '//*[name()="Relationship" and contains(@Type, "hyperlink")]'
+};
+
+/**
+ * The meat of the script, images and text content are extracted here
+ * @param pathToDocument the path to the document relative to the root of the zip
+ */
async function extractFileContents(pathToDocument: string): Promise<DocumentContents> {
console.log('Extracting text...');
const zip = new StreamZip({ file: pathToDocument, storeEntries: true });
@@ -261,48 +367,68 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
// extract the body of the document and, specifically, its captions
const document = await Utilities.readAndParseXml(zip, "word/document.xml");
+ // get plain text
const body = document.root()?.text() ?? "No body found. Check the import script's XML parser.";
const captions: string[] = [];
const embeddedFileNames: string[] = [];
- const captionTargets = document.find(tableCellXPath).map(node => node.text().trim());
- const paragraphs = document.find('//*[name()="w:p"]').map(node => Utilities.correctSentences(node.text()).transformed!);
+ // preserve paragraph formatting and line breaks that would otherwise get lost in the plain text parsing
+ // of the XML hierarchy
+ const paragraphs = document.find(xPaths.paragraphs).map(node => Utilities.correctSentences(node.text()).transformed!);
const start = paragraphs.indexOf(paragraphs.find(el => /Bill Buxton[’']s Notes/.test(el))!) + 1;
const end = paragraphs.indexOf("Device Details");
const longDescription = paragraphs.slice(start, end).filter(paragraph => paragraph.length).join("\n\n");
- const { length } = captionTargets;
- strictEqual(length > 3, true, "No captions written.");
- strictEqual(length % 3 === 0, true, "Improper caption formatting.");
-
- for (let i = 3; i < captionTargets.length; i += 3) {
- const row = captionTargets.slice(i, i + 3);
+ // extract captions from the table cells
+ const tableRowsFlattened = document.find(xPaths.tableCells).map(node => node.text().trim());
+ const { length } = tableRowsFlattened;
+ const numCols = 3;
+ strictEqual(length > numCols, true, "No captions written."); // first row has the headers, not content
+ strictEqual(length % numCols === 0, true, "Improper caption formatting.");
+
+ // break the flat list of strings into groups of numColumns. Thus, each group represents
+ // a row in the table, where the first row has no text content since it's
+ // the image, the second has the file name and the third has the caption (maybe additional columns
+ // have been added or reordered since this was written, but follow the same appraoch)
+ for (let i = numCols; i < tableRowsFlattened.length; i += numCols) {
+ const row = tableRowsFlattened.slice(i, i + numCols);
embeddedFileNames.push(row[1]);
captions.push(row[2]);
}
// extract all hyperlinks embedded in the document
const rels = await Utilities.readAndParseXml(zip, "word/_rels/document.xml.rels");
- const hyperlinks = rels.find(hyperlinkXPath).map(el => el.attrs()[2].value());
+ const hyperlinks = rels.find(xPaths.hyperlinks).map(el => el.attrs()[2].value());
console.log("Text extracted.");
+ // write out the images for this document
console.log("Beginning image extraction...");
const imageData = await writeImages(zip);
console.log(`Extracted ${imageData.length} images.`);
+ // cleanup
zip.close();
return { body, longDescription, imageData, captions, embeddedFileNames, hyperlinks };
}
+// zip relative path from root expression / filter used to isolate only media assets
const imageEntry = /^word\/media\/\w+\.(jpeg|jpg|png|gif)/;
-interface Dimensions {
+/**
+ * Image dimensions and file suffix,
+ */
+interface ImageAttrs {
width: number;
height: number;
type: string;
}
+/**
+ * For each image, stream the file, get its size, check if it's an icon
+ * (if it is, ignore it)
+ * @param zip the zip instance data source
+ */
async function writeImages(zip: any): Promise<ImageData[]> {
const allEntries = Object.values<any>(zip.entries()).map(({ name }) => name);
const imageEntries = allEntries.filter(name => imageEntry.test(name));
@@ -315,8 +441,8 @@ async function writeImages(zip: any): Promise<ImageData[]> {
});
for (const mediaPath of imageEntries) {
- const { width, height, type } = await new Promise<Dimensions>(async resolve => {
- const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: Dimensions) => {
+ const { width, height, type } = await new Promise<ImageAttrs>(async resolve => {
+ const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: ImageAttrs) => {
readStream.destroy();
resolve(dimensions);
}).on("error", () => readStream.destroy());
@@ -324,11 +450,26 @@ async function writeImages(zip: any): Promise<ImageData[]> {
readStream.pipe(sizeStream);
});
- if (Math.abs(width - height) > 10) {
- valid.push({ width, height, type, mediaPath });
+ // if it's not an icon, by this rough heuristic, i.e. is it not square
+ const number = Number(/image(\d+)/.exec(mediaPath)![1]);
+ if (number > 5 || width - height > 10) {
+ valid.push({ width, height, type, mediaPath, number });
}
}
+ valid.sort((a, b) => a.number - b.number);
+
+ const [{ width: first_w, height: first_h }, { width: second_w, height: second_h }] = valid;
+ if (Math.abs(first_w / second_w - first_h / second_h) < 0.01) {
+ const first_size = first_w * first_h;
+ const second_size = second_w * second_h;
+ const target = first_size >= second_size ? 1 : 0;
+ valid.splice(target, 1);
+ console.log(`Heuristically removed image with size ${target ? second_size : first_size}`);
+ }
+
+ // for each valid image, output the _o, _l, _m, and _s files
+ // THIS IS WHERE THE SCRIPT SPENDS MOST OF ITS TIME
for (const { type, width, height, mediaPath } of valid) {
const generatedFileName = `upload_${Utils.GenerateGuid()}.${type.toLowerCase()}`;
await DashUploadUtils.outputResizedImages(() => getImageStream(mediaPath), generatedFileName, imageDir);
@@ -342,6 +483,14 @@ async function writeImages(zip: any): Promise<ImageData[]> {
return imageUrls;
}
+/**
+ * Takes the results of extractFileContents, which relative to this is sort of the
+ * external media / preliminary text processing, and now tests the given file name to
+ * with those value definitions to make sure the body of the document contains all
+ * required fields, properly formatted
+ * @param fileName the file whose body to inspect
+ * @param contents the data already computed / parsed by extractFileContents
+ */
function analyze(fileName: string, contents: DocumentContents): AnalysisResult {
const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescription } = contents;
const device: any = {
@@ -354,43 +503,56 @@ function analyze(fileName: string, contents: DocumentContents): AnalysisResult {
const errors: { [key: string]: string } = { fileName };
for (const key of deviceKeys) {
- const { exp, transformer, matchIndex, required } = RegexMap.get(key)!;
+ const { exp, transformer, matchIndex, required } = FormatMap.get(key)!;
const matches = exp.exec(body);
let captured: string;
- if (matches && (captured = matches[matchIndex ?? 1])) {
- captured = captured.replace(/\s{2,}/g, " ");
+ // if we matched and we got the specific match we're after
+ if (matches && (captured = matches[matchIndex ?? 1])) { // matchIndex defaults to 1
+ captured = captured.replace(/\s{2,}/g, " "); // remove excess whitespace
+ // if supplied, apply the required transformation (recall this is specified in FormatMap)
if (transformer) {
const { error, transformed } = transformer(captured);
if (error) {
+ // we hit a snag trying to transform the valid match
+ // still counts as a fundamental error
errors[key] = `__ERR__${key.toUpperCase()}__TRANSFORM__: ${error}`;
continue;
}
captured = transformed;
}
-
device[key] = captured;
} else if (required ?? true) {
+ // the field was either implicitly or explicitly required, and failed to match the definition in
+ // FormatMap
errors[key] = `ERR__${key.toUpperCase()}__: outer match ${matches === null ? "wasn't" : "was"} captured.`;
continue;
}
}
+ // print errors - this can be removed
const errorKeys = Object.keys(errors);
if (errorKeys.length > 1) {
console.log(red(`@ ${cyan(fileName.toUpperCase())}...`));
errorKeys.forEach(key => key !== "filename" && console.log(red(errors[key])));
- return { errors };
+ return { invalid: errors };
}
return { device };
}
+/**
+ * A utility function that writes the JSON results for this import out to the desired path
+ * @param relativePath where to write the JSON file
+ * @param data valid device document objects, or errors
+ * @param total used for more informative printing
+ * @param success whether or not the caller is writing the successful parses or the failures
+ */
async function writeOutputFile(relativePath: string, data: any[], total: number, success: boolean) {
console.log(yellow(`Encountered ${data.length} ${success ? "valid" : "invalid"} documents out of ${total} candidates. Writing ${relativePath}...`));
return new Promise<void>((resolve, reject) => {
const destination = path.resolve(outDir, relativePath);
- const contents = JSON.stringify(data, undefined, 4);
+ const contents = JSON.stringify(data, undefined, 4); // format the JSON
writeFile(destination, contents, err => err ? reject(err) : resolve());
});
} \ No newline at end of file
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 1441a8621..ed122e544 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -16,7 +16,7 @@ filesPath = "../../server/public/files"
image_dist = filesPath + "/images/buxton"
db = MongoClient("localhost", 27017)["Dash"]
-target_collection = db.newDocuments
+target_collection = db.documents
target_doc_title = "Collection 1"
schema_guids = []
common_proto_id = ""
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts
index e2b01d585..27e9de065 100644
--- a/src/server/ApiManagers/ApiManager.ts
+++ b/src/server/ApiManagers/ApiManager.ts
@@ -1,4 +1,4 @@
-import RouteManager, { RouteInitializer } from "../RouteManager";
+import { RouteInitializer } from "../RouteManager";
export type Registration = (initializer: RouteInitializer) => void;
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 9e70af2eb..7fbb37658 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,12 +1,12 @@
import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied, PublicHandler } from "../RouteManager";
-import { WebSocket } from "../Websocket/Websocket";
+import { Method, _permission_denied } from "../RouteManager";
+import { WebSocket } from "../websocket";
import { Database } from "../database";
import rimraf = require("rimraf");
-import { pathToDirectory, Directory } from "./UploadManager";
import { filesDirectory } from "..";
import { DashUploadUtils } from "../DashUploadUtils";
import { mkdirSync } from "fs";
+import RouteSubscriber from "../RouteSubscriber";
export default class DeleteManager extends ApiManager {
@@ -14,68 +14,39 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
- subscription: "/delete",
- secureHandler: async ({ res, isRelease }) => {
+ subscription: new RouteSubscriber("delete").add("target?"),
+ secureHandler: async ({ req, res, isRelease }) => {
if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
+ return _permission_denied(res, "Cannot perform a delete operation outside of the development environment!");
}
- await WebSocket.deleteFields();
- res.redirect("/home");
- }
- });
- register({
- method: Method.GET,
- subscription: "/deleteAll",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
+ const { target } = req.params;
+ const { doDelete } = WebSocket;
+
+ if (!target) {
+ await doDelete();
+ } else {
+ let all = false;
+ switch (target) {
+ case "all":
+ all = true;
+ case "database":
+ await doDelete(false);
+ if (!all) break;
+ case "files":
+ rimraf.sync(filesDirectory);
+ mkdirSync(filesDirectory);
+ await DashUploadUtils.buildFileDirectories();
+ break;
+ default:
+ await Database.Instance.dropSchema(target);
+ }
}
- await WebSocket.deleteAll();
- res.redirect("/home");
- }
- });
- register({
- method: Method.GET,
- subscription: "/deleteAssets",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
- }
- rimraf.sync(filesDirectory);
- mkdirSync(filesDirectory);
- await DashUploadUtils.buildFileDirectories();
- res.redirect("/delete");
- }
- });
-
- register({
- method: Method.GET,
- subscription: "/deleteWithAux",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
- }
- await Database.Auxiliary.DeleteAll();
- res.redirect("/delete");
- }
- });
-
- register({
- method: Method.GET,
- subscription: "/deleteWithGoogleCredentials",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
- }
- await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll();
- res.redirect("/delete");
+ res.redirect("/home");
}
});
}
-}
-
-const deletionPermissionError = "Cannot perform a delete operation outside of the development environment!";
+} \ No newline at end of file
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index a5240edbc..f94b77cac 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -1,10 +1,8 @@
import ApiManager, { Registration } from "./ApiManager";
import { Method, _permission_denied } from "../RouteManager";
import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import { Database } from "../database";
import RouteSubscriber from "../RouteSubscriber";
-
-const deletionPermissionError = "Cannot perform specialized delete outside of the development environment!";
+import { Database } from "../database";
const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
["create", (api, params) => api.create(params)],
@@ -20,11 +18,11 @@ export default class GeneralGoogleManager extends ApiManager {
method: Method.GET,
subscription: "/readGoogleAccessToken",
secureHandler: async ({ user, res }) => {
- const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
- if (!token) {
+ const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id));
+ if (!credentials?.access_token) {
return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
}
- return res.send(token);
+ return res.send(credentials);
}
});
@@ -37,6 +35,15 @@ export default class GeneralGoogleManager extends ApiManager {
});
register({
+ method: Method.GET,
+ subscription: "/revokeGoogleAccessToken",
+ secureHandler: async ({ user, res }) => {
+ await Database.Auxiliary.GoogleAccessToken.Revoke(user.id);
+ res.send();
+ }
+ });
+
+ register({
method: Method.POST,
subscription: new RouteSubscriber("googleDocs").add("sector", "action"),
secureHandler: async ({ req, res, user }) => {
diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts
index 88219423d..be17b698e 100644
--- a/src/server/ApiManagers/GooglePhotosManager.ts
+++ b/src/server/ApiManagers/GooglePhotosManager.ts
@@ -3,7 +3,7 @@ import { Method, _error, _success, _invalid } from "../RouteManager";
import * as path from "path";
import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
import { BatchedArray, TimeUnit } from "array-batcher";
-import { Opt } from "../../new_fields/Doc";
+import { Opt } from "../../fields/Doc";
import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils";
import { Database } from "../database";
import { red } from "colors";
@@ -56,7 +56,7 @@ export default class GooglePhotosManager extends ApiManager {
const { media } = req.body;
// first we need to ensure that we know the google account to which these photos will be uploaded
- const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
+ const token = (await GoogleApiServerUtils.retrieveCredentials(user.id))?.credentials?.access_token;
if (!token) {
return _error(res, authenticationError);
}
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index bcaa6598f..fa2f6002a 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -55,7 +55,7 @@ export default class SessionManager extends ApiManager {
register({
method: Method.GET,
- subscription: this.secureSubscriber("delete"),
+ subscription: this.secureSubscriber("deleteSession"),
secureHandler: this.authorizedAction(async ({ res }) => {
const { error } = await sessionAgent.serverWorker.emit("delete");
res.send(error ? error.message : "Your request was successful: the server successfully deleted the database. Return to /home.");
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 98f029c7d..b185d3b55 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -171,7 +171,7 @@ export default class UploadManager extends ApiManager {
await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => {
err && console.log(err);
res();
- }, true, "newDocuments"))));
+ }, true))));
} catch (e) { console.log(e); }
unlink(path_2, () => { });
}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index d9d346cc1..0d1d8f218 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -3,7 +3,7 @@ import { Method } from "../RouteManager";
import { Database } from "../database";
import { msToTime } from "../ActionUtilities";
import * as bcrypt from "bcrypt-nodejs";
-import { Opt } from "../../new_fields/Doc";
+import { Opt } from "../../fields/Doc";
export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
@@ -89,8 +89,6 @@ export default class UserManager extends ApiManager {
}
});
-
-
register({
method: Method.GET,
subscription: "/activity",
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 5cbba13de..ab3dfffcc 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -2,7 +2,7 @@ import { Email, pathFromRoot } from "../ActionUtilities";
import { red, yellow, green, cyan } from "colors";
import { get } from "request-promise";
import { Utils } from "../../Utils";
-import { WebSocket } from "../Websocket/Websocket";
+import { WebSocket } from "../websocket";
import { MessageStore } from "../Message";
import { launchServer, onWindows } from "..";
import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from "fs";
@@ -37,7 +37,7 @@ export class DashSessionAgent extends AppliedSessionAgent {
monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to));
monitor.on("backup", this.backup);
monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to));
- monitor.on("delete", WebSocket.deleteFields);
+ monitor.on("delete", WebSocket.doDelete);
monitor.coreHooks.onCrashDetected(this.dispatchCrashReport);
return sessionKey;
}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 3f903a861..b74904ada 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -4,7 +4,7 @@ import * as path from 'path';
import * as sharp from 'sharp';
import request = require('request-promise');
import { ExifImage } from 'exif';
-import { Opt } from '../new_fields/Doc';
+import { Opt } from '../fields/Doc';
import { AcceptibleMedia, Upload } from './SharedMediaTypes';
import { filesDirectory, publicDirectory } from '.';
import { File } from 'formidable';
@@ -325,12 +325,7 @@ export namespace DashUploadUtils {
const outputPath = path.resolve(outputDirectory, writtenFiles[suffix] = InjectSize(outputFileName, suffix));
await new Promise<void>(async (resolve, reject) => {
const source = streamProvider();
- let readStream: Stream;
- if (source instanceof Promise) {
- readStream = await source;
- } else {
- readStream = source;
- }
+ let readStream: Stream = source instanceof Promise ? await source : source;
if (resizer) {
readStream = readStream.pipe(resizer.withMetadata());
}
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 5729c3ee5..24745cbb4 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -76,7 +76,7 @@ async function GarbageCollect(full: boolean = true) {
if (!fetchIds.length) {
continue;
}
- const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res, "newDocuments"));
+ const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res));
for (const doc of docs) {
const id = doc.id;
if (doc === undefined) {
@@ -116,10 +116,10 @@ async function GarbageCollect(full: boolean = true) {
const count = Math.min(toDelete.length, 5000);
const toDeleteDocs = toDelete.slice(i, i + count);
i += count;
- const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }, "newDocuments");
+ const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } });
deleted += result.deletedCount || 0;
}
- // const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments");
+ // const result = await Database.Instance.delete({ _id: { $in: toDelete } });
console.log(`${deleted} documents deleted`);
await Search.deleteDocuments(toDelete);
diff --git a/src/server/IDatabase.ts b/src/server/IDatabase.ts
index 6a63df485..dd4968579 100644
--- a/src/server/IDatabase.ts
+++ b/src/server/IDatabase.ts
@@ -2,7 +2,6 @@ import * as mongodb from 'mongodb';
import { Transferable } from './Message';
export const DocumentsCollection = 'documents';
-export const NewDocumentsCollection = 'newDocuments';
export interface IDatabase {
update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): Promise<void>;
updateMany(query: any, update: any, collectionName?: string): Promise<mongodb.WriteOpResult>;
@@ -12,12 +11,13 @@ export interface IDatabase {
delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
- deleteAll(collectionName?: string, persist?: boolean): Promise<any>;
+ dropSchema(...schemaNames: string[]): Promise<any>;
insert(value: any, collectionName?: string): Promise<void>;
getDocument(id: string, fn: (result?: Transferable) => void, collectionName?: string): void;
getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName?: string): void;
+ getCollectionNames(): Promise<string[]>;
visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName?: string): Promise<void>;
query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise<mongodb.Cursor>;
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index 543f96e7f..1f1d702d9 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -1,4 +1,4 @@
-import { IDatabase, DocumentsCollection, NewDocumentsCollection } from './IDatabase';
+import { IDatabase, DocumentsCollection } from './IDatabase';
import { Transferable } from './Message';
import * as mongodb from 'mongodb';
@@ -15,6 +15,10 @@ export class MemoryDatabase implements IDatabase {
}
}
+ public getCollectionNames() {
+ return Promise.resolve(Object.keys(this.db));
+ }
+
public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
const collection = this.getCollection(collectionName);
const set = "$set";
@@ -41,7 +45,7 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve(undefined);
}
- public updateMany(query: any, update: any, collectionName = NewDocumentsCollection): Promise<mongodb.WriteOpResult> {
+ public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.WriteOpResult> {
throw new Error("Can't updateMany a MemoryDatabase");
}
@@ -58,8 +62,15 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve({} as any);
}
- public deleteAll(collectionName = DocumentsCollection, _persist = true): Promise<any> {
- delete this.db[collectionName];
+ public async dropSchema(...schemaNames: string[]): Promise<any> {
+ const existing = await this.getCollectionNames();
+ let valid: string[];
+ if (schemaNames.length) {
+ valid = schemaNames.filter(collection => existing.includes(collection));
+ } else {
+ valid = existing;
+ }
+ valid.forEach(schemaName => delete this.db[schemaName]);
return Promise.resolve();
}
@@ -69,14 +80,14 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve();
}
- public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = NewDocumentsCollection): void {
+ public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection): void {
fn(this.getCollection(collectionName)[id]);
}
public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection): void {
fn(ids.map(id => this.getCollection(collectionName)[id]));
}
- public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = NewDocumentsCollection): Promise<void> {
+ public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = DocumentsCollection): Promise<void> {
const visited = new Set<string>();
while (ids.length) {
const count = Math.min(ids.length, 1000);
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 01aae5de7..80f372733 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,6 +1,6 @@
import { Utils } from "../Utils";
import { Point } from "../pen-gestures/ndollar";
-import { Doc } from "../new_fields/Doc";
+import { Doc } from "../fields/Doc";
import { Image } from "canvas";
import { AnalysisResult, ImportResults } from "../scraping/buxton/final/BuxtonImporter";
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
index aacdb4053..423ce9b46 100644
--- a/src/server/Recommender.ts
+++ b/src/server/Recommender.ts
@@ -1,6 +1,6 @@
-// //import { Doc } from "../new_fields/Doc";
-// //import { StrCast } from "../new_fields/Types";
-// //import { List } from "../new_fields/List";
+// //import { Doc } from "../fields/Doc";
+// //import { StrCast } from "../fields/Types";
+// //import { List } from "../fields/List";
// //import { CognitiveServices } from "../client/cognitive_services/CognitiveServices";
// // var w2v = require('word2vec');
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 80e4a6741..b23215996 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,5 +1,5 @@
import RouteSubscriber from "./RouteSubscriber";
-import { DashUserModel } from "./authentication/models/user_model";
+import { DashUserModel } from "./authentication/DashUserModel";
import { Request, Response, Express } from 'express';
import { cyan, red, green } from 'colors';
diff --git a/src/server/credentials/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts
index e3f4d167b..e3f4d167b 100644
--- a/src/server/credentials/CredentialsLoader.ts
+++ b/src/server/apis/google/CredentialsLoader.ts
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 0f75833ee..20f96f432 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -1,11 +1,11 @@
import { google } from "googleapis";
import { OAuth2Client, Credentials, OAuth2ClientOptions } from "google-auth-library";
-import { Opt } from "../../../new_fields/Doc";
+import { Opt } from "../../../fields/Doc";
import { GaxiosResponse } from "gaxios";
import request = require('request-promise');
-import * as qs from 'query-string';
+import * as qs from "query-string";
import { Database } from "../../database";
-import { GoogleCredentialsLoader } from "../../credentials/CredentialsLoader";
+import { GoogleCredentialsLoader } from "./CredentialsLoader";
/**
* Scopes give Google users fine granularity of control
@@ -149,26 +149,6 @@ export namespace GoogleApiServerUtils {
}
/**
- * Returns the lengthy string or access token that can be passed into
- * the headers of an API request or into the constructor of the Photos
- * client API wrapper.
- * @param userId the Dash user id of the user requesting his/her associated
- * access_token
- * @returns the current access_token associated with the requesting
- * Dash user. The access_token is valid for only an hour, and
- * is then refreshed.
- */
- export async function retrieveAccessToken(userId: string): Promise<string> {
- return new Promise(async resolve => {
- const { credentials } = await retrieveCredentials(userId);
- if (!credentials) {
- return resolve();
- }
- resolve(credentials.access_token!);
- });
- }
-
- /**
* Manipulates a mapping such that, in the limit, each Dash user has
* an associated authenticated OAuth2 client at their disposal. This
* function ensures that the client's credentials always remain up to date
@@ -217,18 +197,6 @@ export namespace GoogleApiServerUtils {
}
/**
- * This is what we return to the server in processNewUser(), after the
- * worker OAuth2Client has used the user-pasted authentication code
- * to retrieve an access token and an info token. The avatar is the
- * URL to the Google-hosted mono-color, single white letter profile 'image'.
- */
- export interface GoogleAuthenticationResult {
- access_token: string;
- avatar: string;
- name: string;
- }
-
- /**
* This method receives the authentication code that the
* user pasted into the overlay in the client side and uses the worker
* and the authentication code to fetch the full set of credentials that
@@ -245,7 +213,7 @@ export namespace GoogleApiServerUtils {
* and display basic user information in the overlay on successful authentication.
* This can be expanded as needed by adding properties to the interface GoogleAuthenticationResult.
*/
- export async function processNewUser(userId: string, authenticationCode: string): Promise<GoogleAuthenticationResult> {
+ export async function processNewUser(userId: string, authenticationCode: string): Promise<EnrichedCredentials> {
const credentials = await new Promise<Credentials>((resolve, reject) => {
worker.getToken(authenticationCode, async (err, credentials) => {
if (err || !credentials) {
@@ -256,13 +224,8 @@ export namespace GoogleApiServerUtils {
});
});
const enriched = injectUserInfo(credentials);
- await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, enriched);
- const { given_name, picture } = enriched.userInfo;
- return {
- access_token: enriched.access_token!,
- avatar: picture,
- name: given_name
- };
+ await Database.Auxiliary.GoogleAccessToken.Write(userId, enriched);
+ return enriched;
}
/**
@@ -316,15 +279,15 @@ export namespace GoogleApiServerUtils {
* @returns the credentials, or undefined if the user has no stored associated credentials,
* and a flag indicating whether or not they were refreshed during retrieval
*/
- async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<Credentials>, refreshed: boolean }> {
- let credentials: Opt<Credentials> = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId);
+ export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>, refreshed: boolean }> {
+ let credentials = await Database.Auxiliary.GoogleAccessToken.Fetch(userId);
let refreshed = false;
if (!credentials) {
return { credentials: undefined, refreshed };
}
// check for token expiry
if (credentials.expiry_date! <= new Date().getTime()) {
- credentials = await refreshAccessToken(credentials, userId);
+ credentials = { ...credentials, ...(await refreshAccessToken(credentials, userId)) };
refreshed = true;
}
return { credentials, refreshed };
@@ -355,7 +318,7 @@ export namespace GoogleApiServerUtils {
});
// expires_in is in seconds, but we're building the new expiry date in milliseconds
const expiry_date = new Date().getTime() + (expires_in * 1000);
- await Database.Auxiliary.GoogleAuthenticationToken.Update(userId, access_token, expiry_date);
+ await Database.Auxiliary.GoogleAccessToken.Update(userId, access_token, expiry_date);
// update the relevant properties
credentials.access_token = access_token;
credentials.expiry_date = expiry_date;
diff --git a/src/server/credentials/google_project_credentials.json b/src/server/apis/google/google_project_credentials.json
index 955c5a3c1..955c5a3c1 100644
--- a/src/server/credentials/google_project_credentials.json
+++ b/src/server/apis/google/google_project_credentials.json
diff --git a/src/server/apis/youtube/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js
index 50b3c7b38..d535bd9ff 100644
--- a/src/server/apis/youtube/youtubeApiSample.js
+++ b/src/server/apis/youtube/youtubeApiSample.js
@@ -1,6 +1,8 @@
const fs = require('fs');
const readline = require('readline');
-const { google } = require('googleapis');
+const {
+ google
+} = require('googleapis');
const OAuth2 = google.auth.OAuth2;
@@ -19,21 +21,27 @@ module.exports.readApiKey = (callback) => {
}
callback(content);
});
-}
+};
module.exports.authorizedGetChannel = (apiKey) => {
//this didnt get called
// Authorize a client with the loaded credentials, then call the YouTube API.
authorize(JSON.parse(apiKey), getChannel);
-}
+};
module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => {
- authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack });
-}
+ authorize(JSON.parse(apiKey), getVideos, {
+ userInput: userInput,
+ callBack: callBack
+ });
+};
module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => {
- authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack });
-}
+ authorize(JSON.parse(apiKey), getVideoDetails, {
+ videoIds: videoIds,
+ callBack: callBack
+ });
+};
/**
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/AuthenticationManager.ts
index f0086d4ea..00f1fe44e 100644
--- a/src/server/authentication/controllers/user_controller.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -1,13 +1,13 @@
-import { default as User, DashUserModel, AuthToken } from "../models/user_model";
+import { default as User, DashUserModel } from "./DashUserModel";
import { Request, Response, NextFunction } from "express";
import * as passport from "passport";
import { IVerifyOptions } from "passport-local";
-import "../config/passport";
+import "./Passport";
import flash = require("express-flash");
import * as async from 'async';
import * as nodemailer from 'nodemailer';
import c = require("crypto");
-import { Utils } from "../../../Utils";
+import { Utils } from "../../Utils";
import { MailOptions } from "nodemailer/lib/stream-transport";
/**
@@ -111,7 +111,7 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
return res.redirect("/signup");
}
- passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
+ passport.authenticate("local", (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
if (err) { next(err); return; }
if (!user) {
return res.redirect("/signup");
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/DashUserModel.ts
index 78e39dbc1..51d920a8f 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -58,11 +58,11 @@ userSchema.pre("save", function save(next) {
if (!user.isModified("password")) {
return next();
}
- bcrypt.genSalt(10, (err, salt) => {
+ bcrypt.genSalt(10, (err: any, salt: string) => {
if (err) {
return next(err);
}
- bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => {
+ bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => {
if (err) {
return next(err);
}
@@ -74,9 +74,9 @@ userSchema.pre("save", function save(next) {
const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
// Choose one of the following bodies for authentication logic.
- // secure
+ // secure (expected, default)
bcrypt.compare(candidatePassword, this.password, cb);
- // bypass password
+ // bypass password (debugging)
// cb(undefined, true);
};
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/Passport.ts
index 286209b20..9b0069414 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/Passport.ts
@@ -1,6 +1,6 @@
import * as passport from 'passport';
import * as passportLocal from 'passport-local';
-import { default as User } from '../models/user_model';
+import { default as User } from './DashUserModel';
const LocalStrategy = passportLocal.Strategy;
diff --git a/src/server/database.ts b/src/server/database.ts
index e9373762c..817712fb0 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,10 +1,10 @@
import * as mongodb from 'mongodb';
import { Transferable } from './Message';
-import { Opt } from '../new_fields/Doc';
+import { Opt } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { Credentials } from 'google-auth-library';
import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
-import { IDatabase } from './IDatabase';
+import { IDatabase, DocumentsCollection } from './IDatabase';
import { MemoryDatabase } from './MemoryDatabase';
import * as mongoose from 'mongoose';
import { Upload } from './SharedMediaTypes';
@@ -15,7 +15,7 @@ export namespace Database {
export let disconnect: Function;
const schema = 'Dash';
const port = 27017;
- export const url = `mongodb://localhost:${port}/`;
+ export const url = `mongodb://localhost:${port}/${schema}`;
enum ConnectionStates {
disconnected = 0,
@@ -48,27 +48,34 @@ export namespace Database {
}
export class Database implements IDatabase {
- public static DocumentsCollection = 'documents';
private MongoClient = mongodb.MongoClient;
private currentWrites: { [id: string]: Promise<void> } = {};
private db?: mongodb.Db;
private onConnect: (() => void)[] = [];
+<<<<<<< HEAD
doConnect() {
+=======
+ async doConnect() {
+>>>>>>> 98c7540fff67c232c1b04f2130ee624f9a70afbd
console.error(`\nConnecting to Mongo with URL : ${url}\n`);
- this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => {
- console.error("mongo connect response\n");
- if (!client) {
- console.error("\nMongo connect failed with the error:\n");
- console.log(_err);
- process.exit(0);
- }
- this.db = client.db();
- this.onConnect.forEach(fn => fn());
+ return new Promise<void>(resolve => {
+ this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => {
+ console.error("mongo connect response\n");
+ if (!client) {
+ console.error("\nMongo connect failed with the error:\n");
+ console.log(_err);
+ process.exit(0);
+ }
+ this.db = client.db();
+ this.onConnect.forEach(fn => fn());
+ resolve();
+ });
});
}
+<<<<<<< HEAD
@@ -77,6 +84,9 @@ export namespace Database {
public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
+=======
+ public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) {
+>>>>>>> 98c7540fff67c232c1b04f2130ee624f9a70afbd
if (this.db) {
const collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
@@ -101,7 +111,7 @@ export namespace Database {
}
}
- public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
+ public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) {
if (this.db) {
const collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
@@ -125,9 +135,21 @@ export namespace Database {
}
}
+ public async getCollectionNames() {
+ const cursor = this.db?.listCollections();
+ const collectionNames: string[] = [];
+ if (cursor) {
+ while (await cursor.hasNext()) {
+ const collection: any = await cursor.next();
+ collection && collectionNames.push(collection.name);
+ }
+ }
+ return collectionNames;
+ }
+
public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
- public delete(id: any, collectionName = Database.DocumentsCollection) {
+ public delete(id: any, collectionName = DocumentsCollection) {
if (typeof id === "string") {
id = { _id: id };
}
@@ -139,25 +161,26 @@ export namespace Database {
}
}
- public async deleteAll(collectionName = Database.DocumentsCollection, persist = true): Promise<any> {
- return new Promise(resolve => {
- const executor = async (database: mongodb.Db) => {
- if (persist) {
- await database.collection(collectionName).deleteMany({});
- } else {
- await database.dropCollection(collectionName);
- }
- resolve();
- };
- if (this.db) {
- executor(this.db);
+ public async dropSchema(...targetSchemas: string[]): Promise<any> {
+ const executor = async (database: mongodb.Db) => {
+ const existing = await Instance.getCollectionNames();
+ let valid: string[];
+ if (targetSchemas.length) {
+ valid = targetSchemas.filter(collection => existing.includes(collection));
} else {
- this.onConnect.push(() => this.db && executor(this.db));
+ valid = existing;
}
- });
+ const pending = Promise.all(valid.map(schemaName => database.dropCollection(schemaName)));
+ return (await pending).every(dropOutcome => dropOutcome);
+ };
+ if (this.db) {
+ return executor(this.db);
+ } else {
+ this.onConnect.push(() => this.db && executor(this.db));
+ }
}
- public async insert(value: any, collectionName = Database.DocumentsCollection) {
+ public async insert(value: any, collectionName = DocumentsCollection) {
if (this.db) {
if ("id" in value) {
value._id = value.id;
@@ -185,7 +208,7 @@ export namespace Database {
}
}
- public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = "newDocuments") {
+ public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) {
if (this.db) {
this.db.collection(collectionName).findOne({ _id: id }, (err, result) => {
if (result) {
@@ -201,7 +224,7 @@ export namespace Database {
}
}
- public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
+ public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) {
if (this.db) {
this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => {
if (err) {
@@ -219,7 +242,7 @@ export namespace Database {
}
}
- public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = "newDocuments"): Promise<void> {
+ public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = DocumentsCollection): Promise<void> {
if (this.db) {
const visited = new Set<string>();
while (ids.length) {
@@ -246,7 +269,7 @@ export namespace Database {
}
}
- public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = "newDocuments"): Promise<mongodb.Cursor> {
+ public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.Cursor> {
if (this.db) {
let cursor = this.db.collection(collectionName).find(query);
if (projection) {
@@ -260,7 +283,7 @@ export namespace Database {
}
}
- public updateMany(query: any, update: any, collectionName = "newDocuments") {
+ public updateMany(query: any, update: any, collectionName = DocumentsCollection) {
if (this.db) {
const db = this.db;
return new Promise<mongodb.WriteOpResult>(res => db.collection(collectionName).update(query, update, (_, result) => res(result)));
@@ -285,14 +308,28 @@ export namespace Database {
}
}
- export const Instance: IDatabase = getDatabase();
+ export const Instance = getDatabase();
+ /**
+ * Provides definitions and apis for working with
+ * portions of the database not dedicated to storing documents
+ * or Dash-internal user data.
+ */
export namespace Auxiliary {
+ /**
+ * All the auxiliary MongoDB collections (schemas)
+ */
export enum AuxiliaryCollections {
- GooglePhotosUploadHistory = "uploadedFromGooglePhotos"
+ GooglePhotosUploadHistory = "uploadedFromGooglePhotos",
+ GoogleAccess = "googleAuthentication"
}
+ /**
+ * Searches for the @param query in the specified @param collection,
+ * and returns at most the first @param cap results. If @param removeId is true,
+ * as it is by default, each object will be stripped of its database id.
+ */
const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => {
const cursor = await Instance.query(query, undefined, collection);
const results = await cursor.toArray();
@@ -303,54 +340,88 @@ export namespace Database {
}) : slice;
};
+ /**
+ * Searches for the @param query in the specified @param collection,
+ * and returns at most the first result. If @param removeId is true,
+ * as it is by default, each object will be stripped of its database id.
+ * Worth the special case since it converts the Array return type to a single
+ * object of the specified type.
+ */
const SanitizedSingletonQuery = async <T>(query: { [key: string]: any }, collection: string, removeId = true): Promise<Opt<T>> => {
const results = await SanitizedCappedQuery(query, collection, 1, removeId);
return results.length ? results[0] : undefined;
};
+ /**
+ * Checks to see if an image with the given @param contentSize
+ * already exists in the aux database, i.e. has already been downloaded from Google Photos.
+ */
export const QueryUploadHistory = async (contentSize: number) => {
return SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
};
- export namespace GoogleAuthenticationToken {
-
- const GoogleAuthentication = "googleAuthentication";
-
- export type StoredCredentials = Credentials & { _id: string };
+ /**
+ * Records the uploading of the image with the given @param information,
+ * using the given content size as a seed for the database id.
+ */
+ export const LogUpload = async (information: Upload.ImageInformation) => {
+ const bundle = {
+ _id: Utils.GenerateDeterministicGuid(String(information.contentSize)),
+ ...information
+ };
+ return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory);
+ };
+ /**
+ * Manages the storage, retrieval and updating of the access token that
+ * facilitates interactions with all their APIs for a given account.
+ */
+ export namespace GoogleAccessToken {
+
+ /**
+ * Format stored in database.
+ */
+ type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string };
+
+ /**
+ * Retrieves the credentials associaed with @param userId
+ * and optionally removes their database id according to @param removeId.
+ */
export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => {
- return SanitizedSingletonQuery<StoredCredentials>({ userId }, GoogleAuthentication, removeId);
+ return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
};
+ /**
+ * Writes the @param enrichedCredentials to the database, associated
+ * with @param userId for later retrieval and updating.
+ */
export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
- return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, GoogleAuthentication);
+ return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
};
+ /**
+ * Updates the @param access_token and @param expiry_date fields
+ * in the stored credentials associated with @param userId.
+ */
export const Update = async (userId: string, access_token: string, expiry_date: number) => {
const entry = await Fetch(userId, false);
if (entry) {
const parameters = { $set: { access_token, expiry_date } };
- return Instance.update(entry._id, parameters, emptyFunction, true, GoogleAuthentication);
+ return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAccess);
}
};
- export const DeleteAll = () => Instance.deleteAll(GoogleAuthentication, false);
-
- }
-
- export const LogUpload = async (information: Upload.ImageInformation) => {
- const bundle = {
- _id: Utils.GenerateDeterministicGuid(String(information.contentSize)),
- ...information
+ /**
+ * Revokes the credentials associated with @param userId.
+ */
+ export const Revoke = async (userId: string) => {
+ const entry = await Fetch(userId, false);
+ if (entry) {
+ Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess);
+ }
};
- return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory);
- };
- export const DeleteAll = async (persist = false) => {
- const collectionNames = Object.values(AuxiliaryCollections);
- const pendingDeletions = collectionNames.map(name => Instance.deleteAll(name, persist));
- return Promise.all(pendingDeletions);
- };
+ }
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 005ecb1be..d1bac6635 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -11,9 +11,9 @@ import * as qs from 'query-string';
import UtilManager from './ApiManagers/UtilManager';
import { SearchManager } from './ApiManagers/SearchManager';
import UserManager from './ApiManagers/UserManager';
-import { WebSocket } from './Websocket/Websocket';
+import { WebSocket } from './websocket';
import DownloadManager from './ApiManagers/DownloadManager';
-import { GoogleCredentialsLoader } from './credentials/CredentialsLoader';
+import { GoogleCredentialsLoader } from './apis/google/CredentialsLoader';
import DeleteManager from "./ApiManagers/DeleteManager";
import PDFManager from "./ApiManagers/PDFManager";
import UploadManager from "./ApiManagers/UploadManager";
@@ -25,7 +25,6 @@ import { yellow } from "colors";
import { DashSessionAgent } from "./DashSession/DashSessionAgent";
import SessionManager from "./ApiManagers/SessionManager";
import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
-import { Utils } from "../Utils";
export const onWindows = process.platform === "win32";
export let sessionAgent: AppliedSessionAgent;
@@ -125,7 +124,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
// initialize the web socket (bidirectional communication: if a user changes
// a field on one client, that change must be broadcast to all other clients)
- WebSocket.start(isRelease);
+ WebSocket.initialize(isRelease);
}
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 45d2fdd33..91a3cb6bf 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -1,6 +1,4 @@
import { Database } from "./database";
-import { Search } from "./Search";
-import * as path from 'path';
//npx ts-node src/server/remapUrl.ts
@@ -50,7 +48,7 @@ async function update() {
return new Promise(res => Database.Instance.update(doc[0], doc[1], () => {
console.log("wrote " + JSON.stringify(doc[1]));
res();
- }, false, "newDocuments"));
+ }, false));
}));
console.log("Done");
// await Promise.all(updates.map(update => {
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index add607761..4b3094616 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -7,7 +7,7 @@ import * as cookieParser from 'cookie-parser';
import expressFlash = require('express-flash');
import flash = require('connect-flash');
import { Database } from './database';
-import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller';
+import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
const MongoStore = require('connect-mongo')(session);
import RouteManager from './RouteManager';
import * as webpack from 'webpack';
@@ -94,6 +94,8 @@ function determineEnvironment() {
const label = isRelease ? "release" : "development";
console.log(`\nrunning server in ${color(label)} mode`);
+ // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
+ // on the client side, thanks to dotenv in webpack.config.js
let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8");
clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/websocket.ts
index 947aa4289..7278bdc32 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/websocket.ts
@@ -1,18 +1,18 @@
-import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "../Message";
-import { Client } from "../Client";
+import { Utils } from "../Utils";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "./Message";
+import { Client } from "./Client";
import { Socket } from "socket.io";
-import { Database } from "../database";
-import { Search } from "../Search";
+import { Database } from "./database";
+import { Search } from "./Search";
import * as io from 'socket.io';
-import YoutubeApi from "../apis/youtube/youtubeApiSample";
-import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader";
-import { logPort } from "../ActionUtilities";
-import { timeMap } from "../ApiManagers/UserManager";
+import YoutubeApi from "./apis/youtube/youtubeApiSample";
+import { GoogleCredentialsLoader } from "./apis/google/CredentialsLoader";
+import { logPort } from "./ActionUtilities";
+import { timeMap } from "./ApiManagers/UserManager";
import { green } from "colors";
-import { serverPathToFile, Directory } from "../ApiManagers/UploadManager";
import { networkInterfaces } from "os";
-import executeImport from "../../scraping/buxton/final/BuxtonImporter";
+import executeImport from "../scraping/buxton/final/BuxtonImporter";
+import { DocumentsCollection } from "./IDatabase";
export namespace WebSocket {
@@ -21,15 +21,7 @@ export namespace WebSocket {
export const socketMap = new Map<SocketIO.Socket, string>();
export let disconnect: Function;
-
- export async function start(isRelease: boolean) {
- await preliminaryFunctions();
- initialize(isRelease);
- }
-
- async function preliminaryFunctions() {
- }
- function initialize(isRelease: boolean) {
+ export function initialize(isRelease: boolean) {
const endpoint = io();
endpoint.on("connection", function (socket: Socket) {
_socket = socket;
@@ -97,7 +89,7 @@ export namespace WebSocket {
Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
if (isRelease) {
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+ Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false));
}
Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
@@ -111,6 +103,12 @@ export namespace WebSocket {
Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+
+ /**
+ * Whenever we receive the go-ahead, invoke the import script and pass in
+ * as an emitter and a terminator the functions that simply broadcast a result
+ * or indicate termination to the client via the web socket
+ */
Utils.AddServerHandler(socket, MessageStore.BeginBuxtonImport, () => {
executeImport(
deviceOrError => Utils.Emit(socket, MessageStore.BuxtonDocumentResult, deviceOrError),
@@ -158,24 +156,10 @@ export namespace WebSocket {
}
}
- export async function deleteFields() {
- await Database.Instance.deleteAll();
- if (process.env.DISABLE_SEARCH !== "true") {
- await Search.clear();
- }
- await Database.Instance.deleteAll('newDocuments');
- }
-
- // export async function deleteUserDocuments() {
- // await Database.Instance.deleteAll();
- // await Database.Instance.deleteAll('newDocuments');
- // }
-
- export async function deleteAll() {
- await Database.Instance.deleteAll();
- await Database.Instance.deleteAll('newDocuments');
- await Database.Instance.deleteAll('sessions');
- await Database.Instance.deleteAll('users');
+ export async function doDelete(onlyFields = true) {
+ const target: string[] = [];
+ onlyFields && target.push(DocumentsCollection);
+ await Database.Instance.dropSchema(...target);
if (process.env.DISABLE_SEARCH !== "true") {
await Search.clear();
}
@@ -205,11 +189,11 @@ export namespace WebSocket {
}
function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
- Database.Instance.getDocument(id, callback, "newDocuments");
+ Database.Instance.getDocument(id, callback);
}
function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) {
- Database.Instance.getDocuments(ids, callback, "newDocuments");
+ Database.Instance.getDocuments(ids, callback);
}
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
@@ -266,7 +250,7 @@ export namespace WebSocket {
function UpdateField(socket: Socket, diff: Diff) {
Database.Instance.update(diff.id, diff.diff,
- () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+ () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
const docfield = diff.diff.$set || diff.diff.$unset;
if (!docfield) {
return;
@@ -291,7 +275,7 @@ export namespace WebSocket {
}
function DeleteField(socket: Socket, id: string) {
- Database.Instance.delete({ _id: id }, "newDocuments").then(() => {
+ Database.Instance.delete({ _id: id }).then(() => {
socket.broadcast.emit(MessageStore.DeleteField.Message, id);
});
@@ -299,14 +283,14 @@ export namespace WebSocket {
}
function DeleteFields(socket: Socket, ids: string[]) {
- Database.Instance.delete({ _id: { $in: ids } }, "newDocuments").then(() => {
+ Database.Instance.delete({ _id: { $in: ids } }).then(() => {
socket.broadcast.emit(MessageStore.DeleteFields.Message, ids);
});
Search.deleteDocuments(ids);
}
function CreateField(newValue: any) {
- Database.Instance.insert(newValue, "newDocuments");
+ Database.Instance.insert(newValue);
}
}