aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin8196 -> 8196 bytes
-rw-r--r--src/Utils.ts6
-rw-r--r--src/client/DocServer.ts4
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx8
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts2
-rw-r--r--src/client/documents/DocumentTypes.ts7
-rw-r--r--src/client/documents/Documents.ts130
-rw-r--r--src/client/util/CurrentUserUtils.ts458
-rw-r--r--src/client/util/DictationManager.ts23
-rw-r--r--src/client/util/DocumentManager.ts20
-rw-r--r--src/client/util/DragManager.ts10
-rw-r--r--src/client/util/GroupManager.tsx57
-rw-r--r--src/client/util/GroupMemberView.tsx22
-rw-r--r--src/client/util/History.ts4
-rw-r--r--src/client/util/HypothesisUtils.ts5
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx9
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx8
-rw-r--r--src/client/util/InteractionUtils.tsx62
-rw-r--r--src/client/util/SearchUtil.ts14
-rw-r--r--src/client/util/SelectionManager.ts30
-rw-r--r--src/client/util/SettingsManager.scss16
-rw-r--r--src/client/util/SettingsManager.tsx57
-rw-r--r--src/client/util/SharingManager.scss23
-rw-r--r--src/client/util/SharingManager.tsx289
-rw-r--r--src/client/views/AntimodeMenu.tsx10
-rw-r--r--src/client/views/ContextMenu.tsx9
-rw-r--r--src/client/views/ContextMenuItem.tsx21
-rw-r--r--src/client/views/DictationOverlay.tsx2
-rw-r--r--src/client/views/DocComponent.tsx11
-rw-r--r--src/client/views/DocumentButtonBar.tsx109
-rw-r--r--src/client/views/DocumentDecorations.scss87
-rw-r--r--src/client/views/DocumentDecorations.tsx199
-rw-r--r--src/client/views/EditableView.tsx111
-rw-r--r--src/client/views/GestureOverlay.tsx75
-rw-r--r--src/client/views/GlobalKeyHandler.ts132
-rw-r--r--src/client/views/InkStrokeProperties.ts268
-rw-r--r--src/client/views/InkingStroke.tsx98
-rw-r--r--src/client/views/MainView.scss103
-rw-r--r--src/client/views/MainView.tsx705
-rw-r--r--src/client/views/MainViewModal.tsx2
-rw-r--r--src/client/views/OverlayView.tsx13
-rw-r--r--src/client/views/Palette.tsx3
-rw-r--r--src/client/views/PreviewCursor.tsx48
-rw-r--r--src/client/views/PropertiesButtons.scss7
-rw-r--r--src/client/views/PropertiesButtons.tsx487
-rw-r--r--src/client/views/PropertiesDocContextSelector.scss0
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx58
-rw-r--r--src/client/views/PropertiesView.scss (renamed from src/client/views/collections/collectionFreeForm/PropertiesView.scss)71
-rw-r--r--src/client/views/PropertiesView.tsx (renamed from src/client/views/collections/collectionFreeForm/PropertiesView.tsx)330
-rw-r--r--src/client/views/ScriptingRepl.tsx17
-rw-r--r--src/client/views/TemplateMenu.tsx9
-rw-r--r--src/client/views/animationtimeline/Keyframe.tsx2
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx5
-rw-r--r--src/client/views/collections/CollectionDockingView.scss39
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx1049
-rw-r--r--src/client/views/collections/CollectionDockingViewMenu.scss (renamed from src/client/views/collections/ParentDocumentSelector.scss)41
-rw-r--r--src/client/views/collections/CollectionDockingViewMenu.tsx48
-rw-r--r--src/client/views/collections/CollectionLinearView.scss2
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx14
-rw-r--r--src/client/views/collections/CollectionMapView.tsx19
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx2
-rw-r--r--src/client/views/collections/CollectionMenu.scss69
-rw-r--r--src/client/views/collections/CollectionMenu.tsx351
-rw-r--r--src/client/views/collections/CollectionPileView.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx877
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx236
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx50
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss108
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx120
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx39
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx24
-rw-r--r--src/client/views/collections/CollectionSubView.tsx95
-rw-r--r--src/client/views/collections/CollectionTreeView.scss9
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx234
-rw-r--r--src/client/views/collections/CollectionView.tsx207
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx138
-rw-r--r--src/client/views/collections/SchemaTable.tsx216
-rw-r--r--src/client/views/collections/TabDocView.scss22
-rw-r--r--src/client/views/collections/TabDocView.tsx383
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx215
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss68
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx558
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx18
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx72
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss11
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx6
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx1
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx1
-rw-r--r--src/client/views/globalCssVariables.scss8
-rw-r--r--src/client/views/globalCssVariables.scss.d.ts2
-rw-r--r--src/client/views/linking/LinkEditor.tsx35
-rw-r--r--src/client/views/linking/LinkMenu.scss2
-rw-r--r--src/client/views/linking/LinkMenu.tsx21
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx3
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx30
-rw-r--r--src/client/views/nodes/AudioBox.tsx24
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx56
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx10
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx54
-rw-r--r--src/client/views/nodes/DocumentView.scss7
-rw-r--r--src/client/views/nodes/DocumentView.tsx221
-rw-r--r--src/client/views/nodes/FaceRectangles.tsx2
-rw-r--r--src/client/views/nodes/FieldView.tsx6
-rw-r--r--src/client/views/nodes/FilterBox.scss54
-rw-r--r--src/client/views/nodes/FilterBox.tsx215
-rw-r--r--src/client/views/nodes/FontIconBox.scss24
-rw-r--r--src/client/views/nodes/FontIconBox.tsx11
-rw-r--r--src/client/views/nodes/ImageBox.tsx101
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx7
-rw-r--r--src/client/views/nodes/LabelBox.scss1
-rw-r--r--src/client/views/nodes/LinkAnchorBox.scss5
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx10
-rw-r--r--src/client/views/nodes/LinkBox.tsx2
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx24
-rw-r--r--src/client/views/nodes/MenuIconBox.scss49
-rw-r--r--src/client/views/nodes/MenuIconBox.tsx33
-rw-r--r--src/client/views/nodes/PDFBox.tsx25
-rw-r--r--src/client/views/nodes/PresBox.scss8
-rw-r--r--src/client/views/nodes/PresBox.tsx509
-rw-r--r--src/client/views/nodes/RadialMenuItem.tsx8
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx8
-rw-r--r--src/client/views/nodes/SliderBox.tsx5
-rw-r--r--src/client/views/nodes/VideoBox.tsx40
-rw-r--r--src/client/views/nodes/WebBox.scss68
-rw-r--r--src/client/views/nodes/WebBox.tsx108
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx12
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx219
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx40
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts8
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx107
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts14
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx6
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts4
-rw-r--r--src/client/views/pdf/Annotation.tsx18
-rw-r--r--src/client/views/pdf/PDFMenu.tsx18
-rw-r--r--src/client/views/pdf/PDFViewer.tsx95
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx16
-rw-r--r--src/client/views/search/IconBar.tsx25
-rw-r--r--src/client/views/search/IconButton.tsx140
-rw-r--r--src/client/views/search/SearchBox.scss398
-rw-r--r--src/client/views/search/SearchBox.tsx1052
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx24
-rw-r--r--src/fields/DateField.ts4
-rw-r--r--src/fields/Doc.ts90
-rw-r--r--src/fields/List.ts27
-rw-r--r--src/fields/RichTextField.ts2
-rw-r--r--src/fields/RichTextUtils.ts2
-rw-r--r--src/fields/ScriptField.ts16
-rw-r--r--src/fields/documentSchemas.ts12
-rw-r--r--src/fields/util.ts47
-rw-r--r--src/mobile/AudioUpload.tsx9
-rw-r--r--src/mobile/ImageUpload.tsx28
-rw-r--r--src/mobile/MobileInterface.tsx141
-rw-r--r--src/server/ApiManagers/SearchManager.ts2
-rw-r--r--src/server/ApiManagers/UploadManager.ts2
-rw-r--r--src/server/Search.ts11
-rw-r--r--src/server/websocket.ts3
162 files changed, 6207 insertions, 7798 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index 299b902c6..02618014e 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index d9a5353e8..6582e43ef 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -422,6 +422,8 @@ export function returnEmptyString() { return ""; }
export function returnEmptyFilter() { return [] as string[]; }
+export function returnEmptyDoclist() { return [] as any[]; }
+
export let emptyPath = [];
export function emptyFunction() { }
@@ -547,8 +549,8 @@ export function setupMoveUpEvents(
target: object,
e: React.PointerEvent,
moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
- upEvent: (e: PointerEvent, movement: number[]) => void,
- clickEvent: (e: PointerEvent, doubleTap?: boolean) => void,
+ upEvent: (e: PointerEvent, movement: number[]) => any,
+ clickEvent: (e: PointerEvent, doubleTap?: boolean) => any,
stopPropagation: boolean = true,
stopMovePropagation: boolean = true
) {
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 63e01bc5b..9da5b8632 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,7 +5,7 @@ import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../fields/RefField';
import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols';
-import GestureOverlay from './views/GestureOverlay';
+import { GestureOverlay } from './views/GestureOverlay';
import MobileInkOverlay from '../mobile/MobileInkOverlay';
import { runInAction } from 'mobx';
import { ObjectField } from '../fields/ObjectField';
@@ -261,7 +261,7 @@ export namespace DocServer {
} else {
// CACHED => great, let's just return the cached field we have
return Promise.resolve(cached).then(field => {
- (field instanceof Doc) && fetchProto(field);
+ //(field instanceof Doc) && fetchProto(field);
return field;
});
}
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 117d1fa1e..cda108058 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,17 +1,17 @@
-import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
import { Opt } from "../../fields/Doc";
import { Networking } from "../Network";
-import "./GoogleAuthenticationManager.scss";
import { Scripting } from "../util/Scripting";
+import { MainViewModal } from "../views/MainViewModal";
+import "./GoogleAuthenticationManager.scss";
const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
const prompt = "Paste authorization code here...";
@observer
-export default class GoogleAuthenticationManager extends React.Component<{}> {
+export class GoogleAuthenticationManager extends React.Component<{}> {
public static Instance: GoogleAuthenticationManager;
private authenticationLink: Opt<string> = undefined;
@observable private openState = false;
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 92eaf2e73..899e65a16 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -11,7 +11,7 @@ import { Utils } from "../../../Utils";
import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox";
-import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
+import { GoogleAuthenticationManager } from "../GoogleAuthenticationManager";
import Photos = require('googlephotos');
export namespace GooglePhotos {
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1bef6fa08..37a148e55 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -13,7 +13,8 @@ export enum DocumentType {
INK = "inks", // ink stroke
SCREENSHOT = "screenshot", // view of a desktop application
FONTICON = "fonticonbox", // font icon
- SEARCH = "search", // search query
+ FILTER = "filter",
+ SEARCH = "search", // search query
LABEL = "label", // simple text label
BUTTON = "button", // onClick button
WEBCAM = "webcam", // webcam
@@ -32,10 +33,10 @@ export enum DocumentType {
YOUTUBE = "youtube", // youtube directory (view of you tube search results)
DOCHOLDER = "docholder", // nested document (view of a document)
SEARCHITEM = "searchitem",
- COMPARISON = "comparison", // before/after view with slider (view of 2 images)
+ COMPARISON = "comparison", // before/after view with slider (view of 2 images)
GROUP = "group", // group of users
LINKDB = "linkdb", // database of links ??? why do we have this
- SCRIPTDB = "scriptdb", // database of scripts
+ SCRIPTDB = "scriptdb", // database of scripts
GROUPDB = "groupdb" // database of groups
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 42ba4d2c4..8d1f8a04e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,5 +1,5 @@
-import { runInAction, action } from "mobx";
-import { extname, basename } from "path";
+import { runInAction } from "mobx";
+import { basename, extname } from "path";
import { DateField } from "../../fields/DateField";
import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
import { HtmlField } from "../../fields/HtmlField";
@@ -12,28 +12,28 @@ import { ComputedField, ScriptField } from "../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { AudioField, ImageField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
import { MessageStore } from "../../server/Message";
+import { Upload } from "../../server/SharedMediaTypes";
import { OmitKeys, Utils } from "../../Utils";
import { YoutubeBox } from "../apis/youtube/YoutubeBox";
import { DocServer } from "../DocServer";
+import { Networking } from "../Network";
import { DocumentManager } from "../util/DocumentManager";
import { dropActionType } from "../util/DragManager";
import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox";
import { LinkManager } from "../util/LinkManager";
import { Scripting } from "../util/Scripting";
-import { UndoManager } from "../util/UndoManager";
-import { DocumentType } from "./DocumentTypes";
-import { SearchBox } from "../views/search/SearchBox";
+import { undoBatch, UndoManager } from "../util/UndoManager";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
import { ContextMenu } from "../views/ContextMenu";
import { ContextMenuProps } from "../views/ContextMenuItem";
+import { DFLT_IMAGE_NATIVE_DIM } from "../views/globalCssVariables.scss";
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke";
import { AudioBox } from "../views/nodes/AudioBox";
import { ColorBox } from "../views/nodes/ColorBox";
import { ComparisonBox } from "../views/nodes/ComparisonBox";
import { DocHolderBox } from "../views/nodes/DocHolderBox";
import { FontIconBox } from "../views/nodes/FontIconBox";
-import { MenuIconBox } from "../views/nodes/MenuIconBox";
import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
import { ImageBox } from "../views/nodes/ImageBox";
import { KeyValueBox } from "../views/nodes/KeyValueBox";
@@ -47,11 +47,13 @@ import { SliderBox } from "../views/nodes/SliderBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
import { PresElementBox } from "../views/presentationview/PresElementBox";
+import { SearchBox } from "../views/search/SearchBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
-import { Networking } from "../Network";
-import { Upload } from "../../server/SharedMediaTypes";
+import { DocumentType } from "./DocumentTypes";
+import { FilterBox } from "../views/nodes/FilterBox";
const path = require('path');
+const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
export interface DocumentOptions {
system?: boolean;
_autoHeight?: boolean;
@@ -72,6 +74,7 @@ export interface DocumentOptions {
_scrollTop?: number; // scroll location for pdfs
_noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged
_chromeStatus?: string;
+ _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view)
_viewType?: string; // sub type of a collection
_gridGap?: number; // gap between items in masonry view
_xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
@@ -101,12 +104,10 @@ export interface DocumentOptions {
page?: number;
description?: string; // added for links
_viewScale?: number;
- isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
forceActive?: boolean;
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
hideLinkButton?: boolean; // whether the blue link counter button should be hidden
hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
_columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
@@ -128,27 +129,26 @@ export interface DocumentOptions {
isAnnotating?: boolean; // whether we web document is annotation mode where links can't be clicked to allow annotations to be created
opacity?: number;
defaultBackgroundColor?: string;
- isBackground?: boolean;
+ _isBackground?: boolean;
isLinkButton?: boolean;
_columnWidth?: number;
_fontSize?: string;
_fontWeight?: number;
_fontFamily?: string;
- curPage?: number;
- currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
+ _curPage?: number;
+ _currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
+ _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
- currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
activeFrame?: number; // the active frame of a document in a frame base collection
appearFrame?: number; // the frame in which the document appears
presTransition?: number; //the time taken for the transition TO a document
presDuration?: number; //the duration of the slide in presentation view
presProgressivize?: boolean;
- // xArray?: number[];
- // yArray?: number[];
borderRounding?: string;
boxShadow?: string;
dontRegisterChildViews?: boolean;
+ dontRegisterView?: 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
"onChildDoubleClick-rawScript"?: string; // onChildDoubleClick script in raw text form
@@ -178,21 +178,25 @@ export interface DocumentOptions {
clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick 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;
+ useCors?: boolean;
icon?: string;
target?: Doc; // available for use in scripts as the primary target document
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
searchFileTypes?: List<string>; // file types allowed in a search query
strokeWidth?: number;
+ cloneFieldFilter?: List<string>; // fields not to copy when the document is cloned
_stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere
+ freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
- treeViewHideTitle?: boolean; // whether to hide the title of a tree view
+ treeViewHideTitle?: boolean; // whether to hide the top document of a tree view
treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
treeViewOpen?: boolean; // whether this document is expanded in a tree view
treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view
treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
treeViewTruncateTitleWidth?: number;
+ treeViewLockExpandedView?: boolean; // whether the expanded view can be changed
+ treeViewDefaultExpandedView?: string; // default expanded view
limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
// [key: string]: Opt<Field>;
pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
@@ -242,6 +246,10 @@ export namespace Docs {
layout: { view: SearchBox, dataField: defaultDataKey },
options: { _width: 400 }
}],
+ [DocumentType.FILTER, {
+ layout: { view: FilterBox, dataField: defaultDataKey },
+ options: { _width: 400 }
+ }],
[DocumentType.COLOR, {
layout: { view: ColorBox, dataField: defaultDataKey },
options: { _nativeWidth: 220, _nativeHeight: 300 }
@@ -268,7 +276,7 @@ export namespace Docs {
}],
[DocumentType.VID, {
layout: { view: VideoBox, dataField: defaultDataKey },
- options: { currentTimecode: 0 },
+ options: { _currentTimecode: 0 },
}],
[DocumentType.AUDIO, {
layout: { view: AudioBox, dataField: defaultDataKey },
@@ -276,7 +284,7 @@ export namespace Docs {
}],
[DocumentType.PDF, {
layout: { view: PDFBox, dataField: defaultDataKey },
- options: { curPage: 1 }
+ options: { _curPage: 1 }
}],
[DocumentType.IMPORT, {
layout: { view: DirectoryImportBox, dataField: defaultDataKey },
@@ -331,7 +339,7 @@ export namespace Docs {
}],
[DocumentType.INK, {
layout: { view: InkingStroke, dataField: defaultDataKey },
- options: { backgroundColor: "transparent" }
+ options: { _fontFamily: "cursive", backgroundColor: "transparent" }
}],
[DocumentType.SCREENSHOT, {
layout: { view: ScreenshotBox, dataField: defaultDataKey },
@@ -442,7 +450,7 @@ export namespace Docs {
// whatever options pertain to this specific prototype
const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
options.layout = layout.view?.LayoutString(layout.dataField);
- const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
+ const doc = Doc.assign(new Doc(prototypeId, true), { system: true, layoutKey: "layout", ...options });
doc.layout_keyValue = KeyValueBox.LayoutString("");
return doc;
}
@@ -533,7 +541,7 @@ export namespace Docs {
Scripting.addGlobal(Buxton);
- const delegateKeys = ["x", "y", "system", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"];
+ const delegateKeys = ["x", "y", "system", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "removeDropProperties", "treeViewOpen"];
/**
* This function receives the relevant document prototype and uses
@@ -564,6 +572,7 @@ export namespace Docs {
if (!("creationDate" in protoProps)) {
protoProps.creationDate = new DateField;
+ protoProps[`${fieldKey}-lastModified`] = new DateField;
}
protoProps.isPrototype = true;
@@ -575,7 +584,7 @@ export namespace Docs {
// without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
dataDoc[fieldKey + "-annotations"] = new List<Doc>();
- dataDoc.aliases = new List<Doc>();
+ dataDoc.aliases = new List<Doc>([viewDoc]);
proto.links = ComputedField.MakeFunction("links(self)");
@@ -679,17 +688,19 @@ export namespace Docs {
export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, {
- isLinkButton: true, treeViewHideTitle: true, treeViewOpen: false, backgroundColor: "lightBlue", // lightBlue is default color for linking dot and link documents text comment area
- removeDropProperties: new List(["isBackground", "isLinkButton"]), ...options
+ dontRegisterChildViews: true,
+ isLinkButton: true, treeViewHideTitle: true, backgroundColor: "lightBlue", // lightBlue is default color for linking dot and link documents text comment area
+ treeViewExpandedView: "fields", removeDropProperties: new List(["_isBackground", "isLinkButton"]), ...options
}, id);
const linkDocProto = Doc.GetProto(doc);
+ linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document.
linkDocProto.anchor1 = source.doc;
linkDocProto.anchor2 = target.doc;
- linkDocProto.anchor1_timecode = source.doc.currentTimecode || source.doc.displayTimecode;
- linkDocProto.anchor2_timecode = target.doc.currentTimecode || target.doc.displayTimecode;
+ linkDocProto.anchor1_timecode = source.doc._currentTimecode || source.doc.displayTimecode;
+ linkDocProto.anchor2_timecode = target.doc._currentTimecode || target.doc.displayTimecode;
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).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]);
Cast(linkDocProto.proto, Doc, null).layoutKey = undefined;
}
@@ -718,6 +729,7 @@ export namespace Docs {
I._backgroundColor = "transparent";
I._width = options._width;
I._height = options._height;
+ I._fontFamily = "cursive";
I.author = Doc.CurrentUserEmail;
I.rotation = 0;
I.data = new InkField(points);
@@ -749,7 +761,7 @@ export namespace Docs {
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", hideFilterView: true, forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id);
}
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -813,14 +825,19 @@ export namespace Docs {
export function FontIconDocument(options?: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { hideLinkButton: true, ...(options || {}) });
}
+ export function FilterDocument(options?: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.FILTER), undefined, { ...(options || {}) });
+ }
export function PresElementBoxDocument(options?: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) });
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
- const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
- Doc.GetProto(inst).data = new List<Doc>(documents);
+ const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+ const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true });
+ const all = TreeDocument([], { title: "Off-Screen Tabs", freezeChildren: "add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true });
+ Doc.GetProto(inst).data = new List<Doc>([tabs, all]);
return inst;
}
@@ -840,7 +857,7 @@ export namespace Docs {
{
type: type,
content: [
- ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth, config.path))
+ ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))
]
}
]
@@ -868,21 +885,24 @@ export namespace DocUtils {
}
const filteredDocs = docFilters.length ? childDocs.filter(d => {
+ if (d.z) return true;
for (const facetKey of Object.keys(filterFacets)) {
const facet = filterFacets[facetKey];
- const satisfiesFacet = Object.keys(facet).some(value => {
- if (facet[value] === "match") {
- if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc
- const allKeys = Array.from(Object.keys(d));
- allKeys.push(...Object.keys(Doc.GetProto(d)));
- const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
- return keys.some(key => Field.toString(d[key] as Field).includes(value));
- }
- return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
+ const matches = Object.keys(facet).filter(value => facet[value] === "match");
+ const checks = Object.keys(facet).filter(value => facet[value] === "check");
+ const xs = Object.keys(facet).filter(value => facet[value] === "x");
+ const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
+ const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
+ const satisfiesMatchFacets = !matches.length ? true : matches.some(value => {
+ if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc
+ const allKeys = Array.from(Object.keys(d));
+ allKeys.push(...Object.keys(Doc.GetProto(d)));
+ const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
+ return keys.some(key => Field.toString(d[key] as Field).includes(value));
}
- return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
+ return Field.toString(d[facetKey] as Field).includes(value);
});
- if (!satisfiesFacet) {
+ if (!satisfiesCheckFacets || !satisfiesMatchFacets || failsNotEqualFacets) {
return false;
}
}
@@ -982,7 +1002,7 @@ export namespace DocUtils {
created = Docs.Create.StackingDocument(DocListCast(field), resolved);
layout = CollectionView.LayoutString;
} else {
- created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
+ created = Docs.Create.TextDocument("", { ...{ _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, _height: 25, _autoHeight: true }, ...resolved });
layout = FormattedTextBox.LayoutString;
}
if (created) {
@@ -1040,15 +1060,15 @@ export namespace DocUtils {
description: "Add Note ...",
subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
description: ":" + StrCast(note.title),
- event: (args: { x: number, y: number }) => {
+ event: undoBatch((args: { x: number, y: number }) => {
const textDoc = Docs.Create.TextDocument("", {
- _width: 200, x, y, _autoHeight: note._autoHeight !== false,
+ _width: 200, x, y, _autoHeight: note._autoHeight !== false, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1)
});
textDoc.layoutKey = "layout_" + note.title;
textDoc[textDoc.layoutKey] = note;
docTextAdder(textDoc);
- },
+ }),
icon: "eye"
})) as ContextMenuProps[],
icon: "eye"
@@ -1057,7 +1077,7 @@ export namespace DocUtils {
description: "Add Template Doc ...",
subitems: DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({
description: ":" + StrCast(dragDoc.title),
- event: (args: { x: number, y: number }) => {
+ event: undoBatch((args: { x: number, y: number }) => {
const newDoc = Doc.ApplyTemplate(dragDoc);
if (newDoc) {
newDoc.author = Doc.CurrentUserEmail;
@@ -1065,7 +1085,7 @@ export namespace DocUtils {
newDoc.y = y;
docAdder(newDoc);
}
- },
+ }),
icon: "eye"
})) as ContextMenuProps[],
icon: "eye"
@@ -1169,7 +1189,7 @@ export namespace DocUtils {
}
const options = optionsCollection as Doc;
const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
- const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
+ const docFind = `options.data?.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
@@ -1179,7 +1199,7 @@ export namespace DocUtils {
found._backgroundColor = enumeration._backgroundColor || found._backgroundColor;
found._color = enumeration.color || found._color;
} else {
- Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, enumeration));
+ Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, { ...enumeration, system: true }));
}
});
return optionsCollection;
@@ -1202,8 +1222,10 @@ export namespace DocUtils {
proto.text = result.rawText;
proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
if (Upload.isImageInformation(result)) {
- proto["data-nativeWidth"] = (result.nativeWidth > result.nativeHeight) ? 400 * result.nativeWidth / result.nativeHeight : 400;
- proto["data-nativeHeight"] = (result.nativeWidth > result.nativeHeight) ? 400 : 400 / (result.nativeWidth / result.nativeHeight);
+ const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+ proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation;
+ proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim;
+ proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
proto.contentSize = result.contentSize;
}
generatedDocuments.push(doc);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 5954e12dc..7a1c193c1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,29 +1,35 @@
import { computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
-import { Utils } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
-import { UndoManager } from "./UndoManager";
-import { Doc, DocListCast, DocListCastAsync, DataSym } from "../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
import { List } from "../../fields/List";
+import { PrefetchProxy } from "../../fields/Proxy";
+import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
-import { ScriptField, ComputedField } from "../../fields/ScriptField";
-import { Cast, PromiseValue, StrCast, NumCast, BoolCast } from "../../fields/Types";
+import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
+import { ComputedField, ScriptField } from "../../fields/ScriptField";
+import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
-import { DragManager } from "./DragManager";
-import { Scripting } from "./Scripting";
-import { CollectionViewType, CollectionView } 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 { Utils } from "../../Utils";
+import { DocServer } from "../DocServer";
+import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
import { DocumentType } from "../documents/DocumentTypes";
-import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
+import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { MainView } from "../views/MainView";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
import { LabelBox } from "../views/nodes/LabelBox";
+import { OverlayView } from "../views/OverlayView";
+import { DocumentManager } from "./DocumentManager";
+import { DragManager } from "./DragManager";
+import { makeTemplate } from "./DropConverter";
+import { HistoryUtil } from "./History";
import { LinkManager } from "./LinkManager";
-import { Id } from "../../fields/FieldSymbols";
+import { Scripting } from "./Scripting";
+import { SearchUtil } from "./SearchUtil";
+import { SelectionManager } from "./SelectionManager";
+import { UndoManager } from "./UndoManager";
export class CurrentUserUtils {
private static curr_id: string;
@@ -36,7 +42,7 @@ export class CurrentUserUtils {
@computed public static get UserDocument() { return Doc.UserDoc(); }
@observable public static GuestTarget: Doc | undefined;
- @observable public static GuestWorkspace: Doc | undefined;
+ @observable public static GuestDashboard: Doc | undefined;
@observable public static GuestMobile: Doc | undefined;
@observable public static propertiesWidth: number = 0;
@@ -46,14 +52,14 @@ export class CurrentUserUtils {
if (doc["template-button-query"] === undefined) {
const queryTemplate = Docs.Create.MulticolumnDocument(
[
- Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, lockedPosition: true, title: "query", _height: 200, system: true }),
+ Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, _chromeStatus: "disabled", lockedPosition: true, title: "query", _height: 200, system: true }),
Docs.Create.FreeformDocument([], { title: "data", _height: 100, system: true })
],
- { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true }
+ { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true }
);
queryTemplate.isTemplateDoc = makeTemplate(queryTemplate);
doc["template-button-query"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(queryTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle"
});
@@ -72,7 +78,7 @@ export class CurrentUserUtils {
this.mobileTextContainer({},
[this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
doc["template-mobile-button"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(queryTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "mobile button", icon: "mobile"
});
@@ -84,11 +90,11 @@ export class CurrentUserUtils {
Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
Docs.Create.TextDocument("", { title: "text", _height: 100, system: true })
],
- { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true }
+ { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true }
);
slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
doc["template-button-slides"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(slideTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "address-card"
});
@@ -96,6 +102,7 @@ export class CurrentUserUtils {
if (doc["template-button-description"] === undefined) {
const descriptionTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created
+ descriptionTemplate.system = true;
descriptionTemplate[DataSym].layout =
"<div>" +
" <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
@@ -104,14 +111,15 @@ export class CurrentUserUtils {
(descriptionTemplate.proto as Doc).isTemplateDoc = makeTemplate(descriptionTemplate.proto as Doc, true, "descriptionView");
doc["template-button-description"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc,
- removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize"
+ removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize", system: true
});
}
if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to.
const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created
+ linkTemplate.system = true;
Doc.GetProto(linkTemplate).layout =
"<div>" +
" <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" +
@@ -150,9 +158,9 @@ export class CurrentUserUtils {
linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
doc["template-button-link"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(linkTemplate) as any as Doc,
- removeDropProperties: new List<string>(["dropAction"]), title: "link view", icon: "window-maximize"
+ removeDropProperties: new List<string>(["dropAction"]), title: "link view", icon: "window-maximize", system: true
});
}
@@ -182,7 +190,7 @@ export class CurrentUserUtils {
box.isTemplateDoc = makeTemplate(box, true, "switch");
doc["template-button-switch"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(box) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on", system: true
});
@@ -232,7 +240,7 @@ export class CurrentUserUtils {
long.title = "Long Description";
doc["template-button-detail"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(detailView) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize", system: true
});
@@ -392,74 +400,68 @@ export class CurrentUserUtils {
}[] {
if (doc.emptyPresentation === undefined) {
doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true });
+ { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyCollection === undefined) {
doc.emptyCollection = Docs.Create.FreeformDocument([],
- { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform", system: true });
+ { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyPane === undefined) {
- doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, title: "Untitled Collection", system: true });
+ doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyComparison === undefined) {
- doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true });
+ doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
}
if (doc.emptyScript === undefined) {
- doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true });
+ doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyScreenshot === undefined) {
- doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot", system: true });
+ doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) });
}
if (doc.emptyAudio === undefined) {
- doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio", system: true });
+ doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyImage === undefined) {
doc.emptyImage = 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", system: true });
}
if (doc.emptyButton === undefined) {
- doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true });
+ doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0;
}
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", system: true });
+ { _width: 250, _height: 250, title: "container", system: true, cloneFieldFilter: new List<string>(["system"]) });
}
if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true, system: true });
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, useCors: true, system: true, cloneFieldFilter: new List<string>(["system"]) });
}
if (doc.activeMobileMenu === undefined) {
this.setupActiveMobileMenu(doc);
}
return [
- { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.clickFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, },
- { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true },
- { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyImage as Doc },
- { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc, noviceMode: true },
- { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScreenshot as Doc },
- // { title: "Drag a webcam", title: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
- { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
- { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyButton as Doc, noviceMode: true },
-
- { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Present", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
- { toolTip: "Tap to create a search box in a new pane, drag for a search box", title: "Query", icon: "search", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptySearch as Doc },
- { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
- // { title: "Drag an import folder", title: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
+ { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, },
+ { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true },
+ { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc },
+ { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true },
+ { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc },
+ { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
+ { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc, noviceMode: true },
+ { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
+ { toolTip: "Tap to create a search box in a new pane, drag for a search box", title: "Query", icon: "search", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySearch as Doc },
+ { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc },
{ toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
- // { title: "Drag an instance of the device collection", title: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
- // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
- // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
- // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
- // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
- // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
- { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
+ { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyDocHolder as Doc },
{ toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
- { toolTip: "Connect a Google Account", title: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
];
}
-
-
// setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
static async setupCreatorButtons(doc: Doc) {
let alreadyCreatedButtons: string[] = [];
@@ -488,7 +490,8 @@ export class CurrentUserUtils {
dragFactory,
clickFactory,
userDoc: noviceMode ? undefined as any : doc,
- hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode"), system: true
+ hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode"),
+ system: true
}));
if (dragCreatorSet === undefined) {
@@ -504,39 +507,43 @@ export class CurrentUserUtils {
}
static menuBtnDescriptions(doc: Doc): {
- title: string, icon: string, click: string, watchedDocuments?: Doc
+ title: string, target: Doc, icon: string, click: string, watchedDocuments?: Doc
}[] {
this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing
return [
- { title: "Sharing", icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc["sidebar-sharing"] as Doc },
- { title: "Workspace", icon: "desktop", click: 'selectMainMenu(self)' },
- { title: "Catalog", icon: "file", click: 'selectMainMenu(self)' },
- { title: "Archive", icon: "archive", click: 'selectMainMenu(self)' },
- { title: "Import", icon: "upload", click: 'selectMainMenu(self)' },
- { title: "Tools", icon: "wrench", click: 'selectMainMenu(self)' },
- { title: "Help", icon: "question-circle", click: 'selectMainMenu(self)' },
- { title: "Settings", icon: "cog", click: 'selectMainMenu(self)' },
- { title: "User Doc", icon: "address-card", click: 'selectMainMenu(self)' },
+ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' },
+ { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' },
+ { title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
+ { title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
+ { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' },
+ { title: "Filter", target: Cast(doc.myFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' },
+ { title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
+ { title: "Catalog", target: undefined as any, icon: "file", click: 'selectMainMenu(self)' },
+ { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' },
+ { title: "Settings", target: undefined as any, icon: "cog", click: 'selectMainMenu(self)' },
+ { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)' },
];
}
static setupSearchPanel(doc: Doc) {
- if (doc["search-panel"] === undefined) {
- doc["search-panel"] = new PrefetchProxy(Docs.Create.SearchDocument({
- _width: 500, _height: 400, backgroundColor: "dimGray", ignoreClick: true,
+ if (doc.mySearchPanelDoc === undefined) {
+ doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({
+ _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true,
childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", system: true
})) as any as Doc;
}
}
static setupMenuPanel(doc: Doc) {
if (doc.menuStack === undefined) {
- const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, icon, click, watchedDocuments }) =>
+ const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments }) =>
Docs.Create.FontIconDocument({
icon,
iconShape: "square",
title,
+ target,
_backgroundColor: "black",
- _stayInCollection: true,
+ dropAction: "alias",
+ removeDropProperties: new List<string>(["dropAction"]),
childDropAction: "same",
_width: 60,
_height: 60,
@@ -588,7 +595,7 @@ export class CurrentUserUtils {
// SEts up mobile buttons for inside mobile menu
static setupMobileButtons(doc?: Doc, buttons?: string[]) {
const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
- { title: "WORKSPACES", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " },
+ { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " },
{ title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
{ title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
{ title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
@@ -685,11 +692,11 @@ export class CurrentUserUtils {
}
static setupLibrary(userDoc: Doc) {
- return CurrentUserUtils.setupWorkspaces(userDoc);
+ return CurrentUserUtils.setupDashboards(userDoc);
}
- // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker.
- // when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
+ // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker.
+ // when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
static async setupToolsBtnPanel(doc: Doc) {
// setup a masonry view of all he creators
const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc);
@@ -711,92 +718,88 @@ export class CurrentUserUtils {
doc.myColorPicker = new PrefetchProxy(color);
}
- if (doc["sidebar-tools"] === undefined) {
+ if (doc.myTools === undefined) {
const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
- title: "sidebar-tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", hideFilterView: true, forceActive: true, system: true
+ title: "My Tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", forceActive: true, system: true
})) as any as Doc;
- doc["sidebar-tools"] = toolsStack;
+ doc.myTools = toolsStack;
}
}
- static setupWorkspaces(doc: Doc) {
- // setup workspaces library item
- doc.myWorkspaces === undefined;
- if (doc.myWorkspaces === undefined) {
- doc.myWorkspaces = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, system: true
- }));
- }
- if (doc["sidebar-workspaces"] === undefined) {
- const newWorkspace = ScriptField.MakeScript(`createNewWorkspace()`);
- (doc.myWorkspaces as Doc).contextMenuScripts = new List<ScriptField>([newWorkspace!]);
- (doc.myWorkspaces as Doc).contextMenuLabels = new List<string>(["Create New Workspace"]);
-
- const workspaces = doc.myWorkspaces as Doc;
-
- doc["sidebar-workspaces"] = new PrefetchProxy(Docs.Create.TreeDocument([workspaces], {
+ static async setupDashboards(doc: Doc) {
+ // setup dashboards library item
+ await doc.myDashboards;
+ if (doc.myDashboards === undefined) {
+ doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "My Dashboards", _height: 400,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true,
+ treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false,
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
- })) as any as Doc;
+ }));
+ const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
+ (doc.myDashboards as any as Doc).contextMenuScripts = new List<ScriptField>([newDashboard!]);
+ (doc.myDashboards as any as Doc).contextMenuLabels = new List<string>(["Create New Dashboard"]);
}
+ return doc.myDashboards as any as Doc;
}
- static setupCatalog(doc: Doc) {
- doc.myCatalog === undefined;
- if (doc.myCatalog === undefined) {
- doc.myCatalog = new PrefetchProxy(Docs.Create.SchemaDocument([], [], {
- title: "CATALOG", _height: 1000, _fitWidth: true, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false,
- childDropAction: "alias", targetDropAction: "same", _stayInCollection: true, treeViewOpen: true, system: true
+ static async setupPresentations(doc: Doc) {
+ await doc.myPresentations;
+ if (doc.myPresentations === undefined) {
+ doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "My Presentations", _height: 100,
+ treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
+ treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false,
+ lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
}));
+ const newPresentations = ScriptField.MakeScript(`createNewPresentation()`);
+ (doc.myPresentations as any as Doc).contextMenuScripts = new List<ScriptField>([newPresentations!]);
+ (doc.myPresentations as any as Doc).contextMenuLabels = new List<string>(["Create New Presentation"]);
+ const presentations = doc.myPresentations as any as Doc;
}
+ return doc.myPresentations as any as Doc;
+ }
- if (doc["sidebar-catalog"] === undefined) {
- const catalog = doc.myCatalog as Doc;
-
- doc["sidebar-catalog"] = new PrefetchProxy(Docs.Create.TreeDocument([catalog], {
- title: "sidebar-catalog",
+ static setupRecentlyClosedDocs(doc: Doc) {
+ // setup Recently Closed library item
+ doc.myRecentlyClosedDocs === undefined;
+ if (doc.myRecentlyClosedDocs === undefined) {
+ doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "Recently Closed", _height: 500,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true,
+ treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false,
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
- })) as any as Doc;
+ }));
+ const clearAll = ScriptField.MakeScript(`self.data = new List([])`);
+ (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
+ (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
}
}
- static setupRecentlyClosed(doc: Doc) {
+ static setupFilterDocs(doc: Doc) {
// setup Recently Closed library item
- doc.myRecentlyClosed === undefined;
- if (doc.myRecentlyClosed === undefined) {
- doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, treeViewOpen: true, _stayInCollection: true, system: true
- }));
- }
- // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready
- PromiseValue(Cast(doc.myRecentlyClosed, Doc)).then(recent => recent && PromiseValue(recent.data).then(DocListCast));
- if (doc["sidebar-recentlyClosed"] === undefined) {
- const clearAll = ScriptField.MakeScript(`self.data = new List([])`);
- (doc.myRecentlyClosed as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
- (doc.myRecentlyClosed as Doc).contextMenuLabels = new List<string>(["Clear All"]);
-
- const recentlyClosed = doc.myRecentlyClosed as Doc;
-
- doc["sidebar-recentlyClosed"] = new PrefetchProxy(Docs.Create.TreeDocument([recentlyClosed], {
- title: "sidebar-recentlyClosed",
+ doc.myFilter === undefined;
+ if (doc.myFilter === undefined) {
+ doc.myFilter = new PrefetchProxy(Docs.Create.FilterDocument({
+ title: "FilterDoc", _height: 500,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true,
+ treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false,
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
- })) as any as Doc;
+ }));
+ const clearAll = ScriptField.MakeScript(`self.data = new List([])`);
+ (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
+ (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
}
}
static setupUserDoc(doc: Doc) {
- if (doc["sidebar-userDoc"] === undefined) {
+ if (doc.myUserDoc === undefined) {
doc.treeViewOpen = true;
doc.treeViewExpandedView = "fields";
- doc["sidebar-userDoc"] = new PrefetchProxy(Docs.Create.TreeDocument([doc], {
- treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "sidebar-userDoc",
- treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false,
+ doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], {
+ treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "My UserDoc",
+ treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false,
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
})) as any as Doc;
}
@@ -806,9 +809,8 @@ export class CurrentUserUtils {
if (doc.sidebar === undefined) {
const sidebarContainer = new Doc();
sidebarContainer._chromeStatus = "disabled";
- sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()");
+ sidebarContainer.system = true;
doc.sidebar = new PrefetchProxy(sidebarContainer);
- doc.system = true;
}
return doc.sidebar as Doc;
}
@@ -817,9 +819,10 @@ export class CurrentUserUtils {
static async setupSidebarButtons(doc: Doc) {
CurrentUserUtils.setupSidebarContainer(doc);
await CurrentUserUtils.setupToolsBtnPanel(doc);
- CurrentUserUtils.setupWorkspaces(doc);
- CurrentUserUtils.setupCatalog(doc);
- CurrentUserUtils.setupRecentlyClosed(doc);
+ CurrentUserUtils.setupDashboards(doc);
+ CurrentUserUtils.setupPresentations(doc);
+ CurrentUserUtils.setupRecentlyClosedDocs(doc);
+ CurrentUserUtils.setupFilterDocs(doc);
CurrentUserUtils.setupUserDoc(doc);
}
@@ -847,8 +850,8 @@ export class CurrentUserUtils {
}
// sets up the default set of documents to be shown in the Overlay layer
static setupOverlays(doc: Doc) {
- if (doc.myOverlayDocuments === undefined) {
- doc.myOverlayDocuments = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true }));
+ if (doc.myOverlayDocs === undefined) {
+ doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true }));
}
}
@@ -859,34 +862,30 @@ export class CurrentUserUtils {
title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true
}));
}
- if (doc.activePresentation === undefined) {
- doc.activePresentation = Doc.MakeCopy(doc.emptyPresentation as Doc, true);
- }
}
// Sharing sidebar is where shared documents are contained
static setupSharingSidebar(doc: Doc) {
- if (doc["sidebar-sharing"] === undefined) {
- doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Shared Documents", childDropAction: "alias", system: true }));
+ if (doc.mySharedDocs === undefined) {
+ doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true }));
}
}
// Import sidebar is where shared documents are contained
static setupImportSidebar(doc: Doc) {
- if (doc["sidebar-import-documents"] === undefined) {
- doc["sidebar-import-documents"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Imported Documents", forceActive: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 30, lockedPosition: true, _chromeStatus: "disabled" }));
+ if (doc.myImportDocs === undefined) {
+ doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My ImportDocuments", forceActive: true, ignoreClick: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, lockedPosition: true, _chromeStatus: "disabled", system: true }));
}
- if (doc["sidebar-import"] === undefined) {
- const uploads = Cast(doc["sidebar-import-documents"], Doc, null);
+ if (doc.myImportPanel === undefined) {
+ const uploads = Cast(doc.myImportDocs, Doc, null);
const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _backgroundColor: "black", title: "Import", icon: "upload", system: true });
- doc["sidebar-import"] = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "Imported Documents", _yMargin: 20, ignoreClick: true, lockedPosition: true }));
+ doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, lockedPosition: true, system: true }));
}
}
-
static setupClickEditorTemplates(doc: Doc) {
if (doc["clickFuncs-child"] === undefined) {
- // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target
+ // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
"docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ",
{ thisContainer: Doc.name }), {
@@ -933,6 +932,7 @@ export class CurrentUserUtils {
}
static async updateUserDocument(doc: Doc) {
+ doc.system = true;
doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
doc.title = Doc.CurrentUserEmail;
doc.activeInkPen = doc;
@@ -958,11 +958,11 @@ export class CurrentUserUtils {
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupImportSidebar(doc);
this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
- this.setupMenuPanel(doc);
this.setupSearchPanel(doc);
this.setupOverlays(doc); // documents in overlay layer
this.setupDockedButtons(doc); // the bottom bar of font icons
await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
+ this.setupMenuPanel(doc);
doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument();
doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
@@ -975,6 +975,7 @@ export class CurrentUserUtils {
return doc;
}
+
public static async loadCurrentUser() {
return rp.get(Utils.prepend("/getCurrentUser")).then(response => {
if (response) {
@@ -998,12 +999,141 @@ export class CurrentUserUtils {
}
});
}
-}
-Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); },
- "creates a new workspace when called");
+ public static _urlState: HistoryUtil.DocUrl;
+
+ public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => {
+ CurrentUserUtils.MainDocId = doc[Id];
+
+ if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
+ !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]));
+ userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc);
+ }
+ const state = CurrentUserUtils._urlState;
+ if (state.sharing === true && !userDoc) {
+ DocServer.Control.makeReadOnly();
+ } else {
+ fromHistory || HistoryUtil.pushState({
+ type: "doc",
+ docId: doc[Id],
+ readonly: state.readonly,
+ nro: state.nro,
+ sharing: false,
+ });
+ if (state.readonly === true || state.readonly === null) {
+ DocServer.Control.makeReadOnly();
+ } else if (state.safe) {
+ if (!state.nro) {
+ DocServer.Control.makeReadOnly();
+ }
+ CollectionView.SetSafeMode(true);
+ } else if (state.nro || state.nro === null || state.readonly === false) {
+ } else if (doc.readOnly) {
+ DocServer.Control.makeReadOnly();
+ } else {
+ DocServer.Control.makeEditable();
+ }
+ }
+
+ return true;
+ }
+ public static importDocument = () => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.multiple = true;
+ input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
+ input.onchange = async _e => {
+ const upload = Utils.prepend("/uploadDoc");
+ const formData = new FormData();
+ const file = input.files && input.files[0];
+ if (file && file.type === 'application/zip') {
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json !== "error") {
+ const doc = await DocServer.GetRefField(json);
+ if (doc instanceof Doc) {
+ setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
+ docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
+ }
+ }
+ } else if (input.files && input.files.length !== 0) {
+ const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null);
+ const disposer = OverlayView.ShowSpinner();
+ DocListCastAsync(importDocs.data).then(async list => {
+ const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
+ list?.splice(0, 0, ...results);
+ disposer();
+ });
+ } else {
+ console.log("No file selected");
+ }
+ };
+ input.click();
+ }
+
+ public static snapshotDashboard = (userDoc: Doc) => {
+ const copy = CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
+ Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
+ CurrentUserUtils.openDashboard(userDoc, copy);
+ }
+
+ public static createNewDashboard = (userDoc: Doc, id?: string) => {
+ const myPresentations = userDoc.myPresentations as Doc;
+ const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true);
+ const dashboards = Cast(userDoc.myDashboards, Doc) as Doc;
+ const dashboardCount = DocListCast(dashboards.data).length + 1;
+ const emptyPane = Cast(userDoc.emptyPane, Doc, null);
+ emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
+ const freeformOptions: DocumentOptions = {
+ x: 0,
+ y: 400,
+ _width: 1500,
+ _height: 1000,
+ title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
+ };
+ const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
+ const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
+ Doc.AddDocToList(myPresentations, "data", presentation);
+ userDoc.activePresentation = presentation;
+ const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+ const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+ const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
+ const createDashboard = ScriptField.MakeScript(`createNewDashboard()`);
+ dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!]);
+ dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]);
+
+ Doc.AddDocToList(dashboards, "data", dashboardDoc);
+ CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
+ }
+
+ public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); }
+ public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); }
+ public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
+ public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); }
+ public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); }
+ public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); }
+}
+
+Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
+ const copy = Doc.copyDragFactory(dragFactory);
+ if (copy) {
+ CollectionDockingView.AddSplit(copy, "right");
+ const view = DocumentManager.Instance.getFirstDocumentView(copy);
+ view && SelectionManager.SelectDoc(view, false);
+ }
+});
+Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); },
+ "creates a snapshot copy of a dashboard");
+Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); },
+ "creates a new dashboard when called");
+Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); },
+ "creates a new presentation when called");
Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
"returns all the links to the document or its annotations", "(doc: any)");
Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); },
"returns all the links directly to the document", "(doc: any)");
+Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); },
+ "imports files from device directly into the import sidebar");
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 540540642..231e1fa8d 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -6,7 +6,6 @@ import { DocumentType } from "../documents/DocumentTypes";
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 "../../fields/Types";
import { listSpec } from "../../fields/Schema";
import { AudioField, ImageField } from "../../fields/URLField";
@@ -88,7 +87,7 @@ export namespace DictationManager {
export const listen = async (options?: Partial<ListeningOptions>) => {
let results: string | undefined;
- const overlay = options !== undefined && options.useOverlay;
+ const overlay = options?.useOverlay;
if (overlay) {
DictationOverlay.Instance.dictationOverlayVisible = true;
DictationOverlay.Instance.isListening = { interim: false };
@@ -100,11 +99,11 @@ export namespace DictationManager {
Utils.CopyText(results);
if (overlay) {
DictationOverlay.Instance.isListening = false;
- const execute = options && options.tryExecute;
+ const execute = options?.tryExecute;
DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
}
- options && options.tryExecute && await DictationManager.Commands.execute(results);
+ options?.tryExecute && await DictationManager.Commands.execute(results);
}
} catch (e) {
if (overlay) {
@@ -129,12 +128,12 @@ export namespace DictationManager {
}
isListening = true;
- const handler = options ? options.interimHandler : undefined;
- const continuous = options ? options.continuous : undefined;
+ const handler = options?.interimHandler;
+ const continuous = options?.continuous;
const indefinite = continuous && continuous.indefinite;
- const language = options ? options.language : undefined;
- const intra = options && options.delimiters ? options.delimiters.intra : undefined;
- const inter = options && options.delimiters ? options.delimiters.inter : undefined;
+ const language = options?.language;
+ const intra = options?.delimiters?.intra;
+ const inter = options?.delimiters?.inter;
recognizer.onstart = () => console.log("initiating speech recognition session...");
recognizer.interimResults = handler !== undefined;
@@ -154,7 +153,7 @@ export namespace DictationManager {
recognizer.onresult = (e: SpeechRecognitionEvent) => {
current = synthesize(e, intra);
let matchedTerminator: string | undefined;
- if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+ if (options?.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
current = matchedTerminator;
recognizer.abort();
return complete();
@@ -324,7 +323,7 @@ export namespace DictationManager {
["open fields", {
action: (target: DocumentView) => {
const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
- target.props.addDocTab(kvp, "onRight");
+ target.props.addDocTab(kvp, "add:right");
}
}],
@@ -338,7 +337,7 @@ export namespace DictationManager {
const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
proto.data = new RichTextField(proseMirrorState);
proto.backgroundColor = "#eeffff";
- target.props.addDocTab(newBox, "onRight");
+ target.props.addDocTab(newBox, "add:right");
}
}]
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 962294933..420b29559 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -124,7 +124,7 @@ export class DocumentManager {
}
static addRightSplit = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddRightSplit(doc);
+ CollectionDockingView.AddSplit(doc, "right");
finished?.();
}
public jumpToDocument = async (
@@ -160,6 +160,9 @@ export class DocumentManager {
docView.props.Document.hidden = !docView.props.Document.hidden;
}
else {
+ const contView = docContext && getFirstDocView(docContext, originatingDoc);
+ contView && contView.topMost && contView.select(false); // bcz: change this to a function prop: popTab() that will make sure the tab for the document is topmost;
+ docView.select(false);
docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
highlight();
@@ -181,7 +184,7 @@ export class DocumentManager {
// now find the target document within the context
if (targetDoc.displayTimecode) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
- targetDocContext.currentTimecode = targetDoc.displayTimecode;
+ targetDocContext._currentTimecode = targetDoc.displayTimecode;
finished?.();
} else { // no timecode means we need to find the context view and focus on our target
setTimeout(() => {
@@ -212,8 +215,8 @@ export class DocumentManager {
public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
const linkDocs = link ? [link] : DocListCast(doc.links);
SelectionManager.DeselectAll();
- const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); // link docs where 'doc' is anchor1
- const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); // link docs where 'doc' is anchor2
+ const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
+ const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
@@ -222,17 +225,16 @@ export class DocumentManager {
followLinks.forEach(async linkDoc => {
if (linkDoc) {
const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
- (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+ (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
- (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
+ (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
if (target) {
const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
- containerDoc.currentTimecode = targetTimecode;
+ containerDoc._currentTimecode = targetTimecode;
const targetContext = await target?.context as Doc;
const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- console.log(targetNavContext);
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
} else {
finished?.();
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0cca61841..9797f5488 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -117,10 +117,11 @@ export namespace DragManager {
}
export class DocumentDragData {
- constructor(dragDoc: Doc[]) {
+ constructor(dragDoc: Doc[], dropAction?: dropActionType) {
this.draggedDocuments = dragDoc;
this.droppedDocuments = [];
this.offset = [0, 0];
+ this.dropAction = dropAction;
}
draggedDocuments: Doc[];
droppedDocuments: Doc[];
@@ -143,7 +144,7 @@ export namespace DragManager {
linkSourceDocument: Doc;
dontClearTextBox?: boolean;
linkDocument?: Doc;
- linkDropCallback?: (data: LinkDragData) => void;
+ linkDropCallback?: (data: { linkDocument?: Doc }) => void;
}
export class ColumnDragData {
constructor(colKey: SchemaHeaderField) {
@@ -160,7 +161,7 @@ export namespace DragManager {
this.annotationDocument = annotationDoc;
this.offset = [0, 0];
}
- linkedToDoc?: boolean;
+ linkDocument?: Doc;
targetContext: Doc | undefined;
dragDocument: Doc;
annotationDocument: Doc;
@@ -168,6 +169,7 @@ export namespace DragManager {
offset: number[];
dropAction: dropActionType;
userDropAction: dropActionType;
+ linkDropCallback?: (data: { linkDocument?: Doc }) => void;
}
export function MakeDropTarget(
@@ -434,7 +436,7 @@ export namespace DragManager {
const target = document.elementFromPoint(e.x, e.y);
- if (target && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) {
+ if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) {
const autoScrollHandler = () => {
target.dispatchEvent(
new CustomEvent<React.DragEvent>("dashDragAutoScroll", {
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index d03989675..cb15b5081 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,24 +1,19 @@
-import * as React from "react";
-import { observable, action, runInAction, computed } from "mobx";
-import { SelectionManager } from "./SelectionManager";
-import MainViewModal from "../views/MainViewModal";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt, DocListCastAsync } from "../../fields/Doc";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as fa from '@fortawesome/free-solid-svg-icons';
-import { library } from "@fortawesome/fontawesome-svg-core";
-import SharingManager, { User } from "./SharingManager";
-import { Utils } from "../../Utils";
-import * as RequestPromise from "request-promise";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
import Select from 'react-select';
-import "./GroupManager.scss";
-import { StrCast, Cast } from "../../fields/Types";
-import GroupMemberView from "./GroupMemberView";
+import * as RequestPromise from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { Cast, StrCast } from "../../fields/Types";
import { setGroups } from "../../fields/util";
+import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
+import { MainViewModal } from "../views/MainViewModal";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-
-library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown);
+import "./GroupManager.scss";
+import { GroupMemberView } from "./GroupMemberView";
+import { SharingManager, User } from "./SharingManager";
/**
* Interface for options for the react-select component
@@ -29,7 +24,7 @@ export interface UserOptions {
}
@observer
-export default class GroupManager extends React.Component<{}> {
+export class GroupManager extends React.Component<{}> {
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@@ -71,7 +66,7 @@ export default class GroupManager extends React.Component<{}> {
const evaluating = raw.map(async user => {
const userDocument = await DocServer.GetRefField(user.userDocumentId);
if (userDocument instanceof Doc) {
- const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc);
+ const notificationDoc = await Cast(userDocument.mySharedDocs, Doc);
runInAction(() => {
if (notificationDoc instanceof Doc) {
this.users.push(user.email);
@@ -92,6 +87,7 @@ export default class GroupManager extends React.Component<{}> {
const members: string[] = JSON.parse(StrCast(group.members));
if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
});
+ this.currentUserGroups.push("Public");
setGroups(this.currentUserGroups);
});
}
@@ -121,6 +117,7 @@ export default class GroupManager extends React.Component<{}> {
close = () => {
this.isOpen = false;
this.currentGroup = undefined;
+ this.selectedUsers = null;
// this.users = [];
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
@@ -162,7 +159,7 @@ export default class GroupManager extends React.Component<{}> {
* @returns the members of the admin group.
*/
get adminGroupMembers(): string[] {
- return this.getGroup("admin") ? JSON.parse(StrCast(this.getGroup("admin")!.members)) : "";
+ return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : "";
}
/**
@@ -182,7 +179,7 @@ export default class GroupManager extends React.Component<{}> {
*/
createGroupDoc(groupName: string, memberEmails: string[] = []) {
const groupDoc = new Doc;
- groupDoc.groupName = groupName;
+ groupDoc.groupName = groupName.toLowerCase() === "admin" ? "Admin" : groupName;
groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
groupDoc.members = JSON.stringify(memberEmails);
if (memberEmails.includes(Doc.CurrentUserEmail)) {
@@ -285,7 +282,11 @@ export default class GroupManager extends React.Component<{}> {
alert("Please enter a group name");
return;
}
- if (this.getAllGroups().find(group => group.groupName === this.inputRef.current!.value)) { // why do I need a null check here?
+ if (this.inputRef.current.value.toLowerCase() === "admin" && this.getGroup("Admin")) {
+ alert("You cannot override the Admin group");
+ return;
+ }
+ if (this.getGroup(this.inputRef.current.value)) {
alert("Please select a unique group name");
return;
}
@@ -314,7 +315,7 @@ export default class GroupManager extends React.Component<{}> {
<div className={"close-button"} onClick={action(() => {
this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false;
})}>
- <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+ <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
</div>
<input
@@ -398,19 +399,19 @@ export default class GroupManager extends React.Component<{}> {
<div className="group-heading">
<p><b>Manage Groups</b></p>
<button onClick={action(() => this.createGroupModalOpen = true)}>
- <FontAwesomeIcon icon={fa.faPlus} size={"sm"} /> Create Group
+ <FontAwesomeIcon icon={"plus-hexagon"} size={"sm"} /> Create Group
</button>
<div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+ <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
</div>
<div className="main-container">
<div
className="sort-groups"
onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} />
- : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} />
- : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />
+ Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
+ : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
+ : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />
}
</div>
<div className="group-body">
@@ -421,7 +422,7 @@ export default class GroupManager extends React.Component<{}> {
>
<div className="group-name" >{group.groupName}</div>
<div className="group-info" onClick={action(() => this.currentGroup = group)}>
- <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+ <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
</div>
</div>
)}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 531ef988a..4ead01e9f 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,25 +1,21 @@
-import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { observer } from "mobx-react";
-import GroupManager, { UserOptions } from "./GroupManager";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { StrCast } from "../../fields/Types";
-import { action, observable } from "mobx";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as fa from '@fortawesome/free-solid-svg-icons';
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
import Select from "react-select";
import { Doc } from "../../fields/Doc";
+import { StrCast } from "../../fields/Types";
+import { MainViewModal } from "../views/MainViewModal";
+import { GroupManager, UserOptions } from "./GroupManager";
import "./GroupMemberView.scss";
-library.add(fa.faTimes, fa.faTrashAlt);
-
interface GroupMemberViewProps {
group: Doc;
onCloseButtonClick: () => void;
}
@observer
-export default class GroupMemberView extends React.Component<GroupMemberViewProps> {
+export class GroupMemberView extends React.Component<GroupMemberViewProps> {
@observable private memberSort: "ascending" | "descending" | "none" = "none";
@@ -43,7 +39,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
>
</input>
<div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}>
- <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+ <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
{GroupManager.Instance.hasEditAccess(this.props.group) ?
<div className="group-buttons">
@@ -88,7 +84,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
</div>
{hasEditAccess ?
<div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
- <FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} />
+ <FontAwesomeIcon icon={"trash-alt"} size={"sm"} />
</div>
: null}
</div>
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 7b7d4b835..cbe36b401 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,8 +1,8 @@
import { Doc } from "../../fields/Doc";
import { DocServer } from "../DocServer";
-import { MainView } from "../views/MainView";
import * as qs from 'query-string';
import { Utils, OmitKeys } from "../../Utils";
+import { CurrentUserUtils } from "./CurrentUserUtils";
export namespace HistoryUtil {
export interface DocInitializerList {
@@ -197,7 +197,7 @@ export namespace HistoryUtil {
await Promise.all(Object.keys(init).map(id => initDoc(id, init[id])));
}
if (field instanceof Doc) {
- MainView.Instance.openWorkspace(field, true);
+ CurrentUserUtils.openDashboard(Doc.UserDoc(), field, true);
}
}
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 9ede94e4b..4a5b52e1e 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -21,7 +21,7 @@ export namespace Hypothesis {
export const getSourceWebDoc = async (uri: string) => {
const result = await findWebDoc(uri);
console.log(result ? "existing doc found" : "existing doc NOT found");
- return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
+ return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _nativeHeight: 962, _width: 400, useCors: true }); // create and return a new Web doc with given uri if no matching docs are found
};
@@ -34,7 +34,7 @@ export namespace Hypothesis {
const results: Doc[] = [];
await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
- const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
+ const docs = res.docs;
const filteredDocs = docs.filter(doc =>
doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
);
@@ -60,6 +60,7 @@ export namespace Hypothesis {
DocumentLinksButton.AnnotationId = annotationId;
DocumentLinksButton.AnnotationUri = annotationUri;
DocumentLinksButton.StartLink = sourceDoc;
+ DocumentLinksButton.StartLinkView = undefined;
});
} else { // if a link has already been started, complete the link to sourceDoc
runInAction(() => {
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 77f13e9f4..d04270afa 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCloudUploadAlt, faPlus, faTag } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { BatchedArray } from "array-batcher";
import "fs";
@@ -47,7 +45,6 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
constructor(props: FieldViewProps) {
super(props);
- library.add(faTag, faPlus);
const doc = this.props.Document;
this.editingMetadata = this.editingMetadata || false;
this.persistent = this.persistent || false;
@@ -301,7 +298,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
opacity: uploading ? 0 : 1,
transition: "0.4s opacity ease"
}}>
- <FontAwesomeIcon icon={faCloudUploadAlt} color="#FFFFFF" size={"2x"} />
+ <FontAwesomeIcon icon={"cloud-upload-alt"} color="#FFFFFF" size={"2x"} />
</div>
<img
style={{
@@ -366,7 +363,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
opacity: uploading ? 0 : 1,
transition: "0.4s opacity ease"
}}
- icon={isEditing ? faCloudUploadAlt : faTag}
+ icon={isEditing ? "cloud-upload-alt" : "tag"}
color="#FFFFFF"
size={"1x"}
/>
@@ -399,7 +396,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
marginLeft: 6.4,
marginTop: 5.2
}}
- icon={faPlus}
+ icon={"plus"}
size={"1x"}
/>
</div>
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index dcb94e2e0..1870213b9 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -4,7 +4,6 @@ import { EditableView } from "../../views/EditableView";
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 "../../../fields/Doc";
import { StrCast, BoolCast } from "../../../fields/Types";
@@ -24,11 +23,6 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
private valueRef = React.createRef<EditableView>();
private checkRef = React.createRef<HTMLInputElement>();
- constructor(props: KeyValueProps) {
- super(props);
- library.add(faPlus);
- }
-
@computed
public get valid() {
return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder);
@@ -132,7 +126,7 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
</div>
<div onClick={() => this.props.remove(this)} title={"Delete Entry"}>
<FontAwesomeIcon
- icon={faPlus}
+ icon={"plus"}
color={"red"}
size={"1x"}
style={{
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index ae3b3e064..4a203d41f 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -132,7 +132,6 @@ export namespace InteractionUtils {
if (isNaN(scaley)) {
scaley = 1;
}
- console.log(pts.length);
return pts;
}
@@ -140,7 +139,7 @@ export namespace InteractionUtils {
export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
- dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
+ dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
let pts: { X: number; Y: number; }[] = [];
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
@@ -182,13 +181,13 @@ export namespace InteractionUtils {
const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc +
`${(pt.X - left - width / 2) * scalex + width / 2},
${(pt.Y - top - width / 2) * scaley + width / 2} `, "");
- const dashArray = String(Number(width) * Number(dash));
+ const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined;
const defGuid = Utils.GenerateGuid();
const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
const addables = pts.map((pts, i) =>
<svg height="10" width="10">
- <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" stroke-width={1} fill="blue"
+ <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" strokeWidth={1} fill="blue"
onDoubleClick={(e) => { console.log(i); }} pointerEvents="all" cursor="all-scroll"
/>
</svg>);
@@ -210,7 +209,7 @@ export namespace InteractionUtils {
points={strpts}
style={{
filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
- fill: fill ? fill : "transparent",
+ fill: fill ? fill : "none",
opacity: strokeWidth !== width ? 0.5 : undefined,
pointerEvents: pevents as any,
stroke: color ?? "rgb(0, 0, 0)",
@@ -300,19 +299,52 @@ export namespace InteractionUtils {
return points;
case "circle":
+ // const centerX = (right + left) / 2;
+ // const centerY = (bottom + top) / 2;
+ // const radius = bottom - centerY;
+
+ // for (var y = top; y < bottom; y++) {
+ // const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ // points.push({ X: x, Y: y });
+ // }
+ // for (var y = bottom; y > top; y--) {
+ // const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ // const newX = centerX - (x - centerX);
+ // points.push({ X: newX, Y: y });
+ // }
+ // points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
const centerX = (right + left) / 2;
const centerY = (bottom + top) / 2;
- const radius = bottom - centerY;
- for (var y = top; y < bottom; y++) {
- const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
- points.push({ X: x, Y: y });
- }
- for (var y = bottom; y > top; y--) {
- const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
- const newX = centerX - (x - centerX);
- points.push({ X: newX, Y: y });
+ if ((bottom - centerY) < (right - centerX)) {
+ const radius = bottom - centerY;
+ for (var y = top; y < bottom; y++) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ points.push({ X: x, Y: y });
+ }
+ for (var y = bottom; y > top; y--) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ const newX = centerX - (x - centerX);
+ points.push({ X: newX, Y: y });
+ }
+ points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
+
+ } else {
+ //right = bottom
+ //left = top
+ const radius = right - centerX;
+ for (var x = left; x < right; x++) {
+ const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
+ points.push({ X: x, Y: y });
+ }
+ for (var x = right; x > left; x--) {
+ const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
+ const newY = centerY - (y - centerY);
+ points.push({ X: x, Y: newY });
+ }
+ points.push({ X: left, Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((left - centerX), 2))) + centerY });
+
+
}
- points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
return points;
// case "arrow":
// const x1 = left;
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index b63fc8991..b09eff849 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -23,12 +23,14 @@ export namespace SearchUtil {
}
export interface SearchParams {
- hl?: boolean;
+ hl?: string;
"hl.fl"?: string;
start?: number;
rows?: number;
fq?: string;
+ sort?: string;
allowAliases?: boolean;
+ onlyAliases?: boolean;
"facet"?: string;
"facet.field"?: string;
}
@@ -37,8 +39,12 @@ export namespace SearchUtil {
export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
query = query || "*"; //If we just have a filter query, search for * as the query
const rpquery = Utils.prepend("/dashsearch");
- const replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}type_t:${arg}`);
- const gotten = await rp.get(rpquery, { qs: { ...options, sort: "lastModified_d desc", q: replacedQuery } });
+ let replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}*:* AND ${arg}`);
+ if (options.onlyAliases) {
+ const header = query.match(/_[atnb]?:/) ? replacedQuery : "DEFAULT:" + replacedQuery;
+ replacedQuery = `{!join from=id to=proto_i}${header}`;
+ }
+ const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } });
const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten);
if (!returnDocs) {
return result;
@@ -80,6 +86,8 @@ export namespace SearchUtil {
if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || testDoc.proto === undefined || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
theDocs.push(testDoc);
theLines.push([]);
+ } else {
+ result.numFound--;
}
}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 113278593..730db4f29 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,10 +1,10 @@
import { observable, action, runInAction, ObservableMap } from "mobx";
-import { Doc } from "../../fields/Doc";
+import { Doc, Opt } from "../../fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { computedFn } from "mobx-utils";
import { List } from "../../fields/List";
-import { Scripting } from "./Scripting";
-import { DocumentManager } from "./DocumentManager";
+import { CollectionSchemaView } from "../views/collections/CollectionSchemaView";
+import { CollectionViewType } from "../views/collections/CollectionView";
export namespace SelectionManager {
@@ -12,10 +12,16 @@ export namespace SelectionManager {
@observable IsDragging: boolean = false;
SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
+ @observable SelectedSchemaDocument: Doc | undefined;
+ @observable SelectedSchemaCollection: CollectionSchemaView | undefined;
@action
+ SelectSchemaDoc(collectionView: Opt<CollectionSchemaView>, doc: Opt<Doc>) {
+ manager.SelectedSchemaDocument = doc;
+ manager.SelectedSchemaCollection = collectionView;
+ }
+ @action
SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
-
// if doc is not in SelectedDocuments, add it
if (!manager.SelectedDocuments.get(docView)) {
if (!ctrlPressed) {
@@ -26,6 +32,8 @@ export namespace SelectionManager {
docView.props.whenActiveChanged(true);
} else if (!ctrlPressed && Array.from(manager.SelectedDocuments.entries()).length > 1) {
Array.from(manager.SelectedDocuments.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false));
+ manager.SelectedSchemaDocument = undefined;
+ manager.SelectedSchemaCollection = undefined;
manager.SelectedDocuments.clear();
manager.SelectedDocuments.set(docView, true);
}
@@ -42,7 +50,8 @@ export namespace SelectionManager {
}
@action
DeselectAll(): void {
-
+ manager.SelectedSchemaCollection = undefined;
+ manager.SelectedSchemaDocument = undefined;
Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments.clear();
Doc.UserDoc().activeSelection = new List<Doc>([]);
@@ -57,6 +66,9 @@ export namespace SelectionManager {
export function SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
manager.SelectDoc(docView, ctrlPressed);
}
+ export function SelectSchemaDoc(colSchema: Opt<CollectionSchemaView>, document: Opt<Doc>): void {
+ manager.SelectSchemaDoc(colSchema, document);
+ }
// 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
@@ -82,6 +94,12 @@ export namespace SelectionManager {
}
export function SelectedDocuments(): Array<DocumentView> {
- return Array.from(manager.SelectedDocuments.keys());
+ return Array.from(manager.SelectedDocuments.keys()).filter(dv => dv.props.Document._viewType !== CollectionViewType.Docking);
+ }
+ export function SelectedSchemaDoc(): Doc | undefined {
+ return manager.SelectedSchemaDocument;
+ }
+ export function SelectedSchemaCollection(): CollectionSchemaView | undefined {
+ return manager.SelectedSchemaCollection;
}
} \ No newline at end of file
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index ec513e5d5..68e0b91b0 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -4,7 +4,6 @@
//background-color: whitesmoke !important;
color: grey;
width: 450px;
- height: 300px;
button {
background: #315a96;
@@ -142,7 +141,7 @@
.colorFlyout {
margin-top: 2px;
- margin-right: 25px;
+ margin-right: 18px;
&:hover {
cursor: pointer;
@@ -163,6 +162,7 @@
.preferences-color {
display: flex;
+ margin-top: 2px;
.preferences-color-text {
color: black;
@@ -174,6 +174,8 @@
.preferences-font {
display: flex;
+ height: 23px;
+ margin-top: 2px;
.preferences-font-text {
color: black;
@@ -194,6 +196,16 @@
}
}
+ .preferences-check {
+ color: black;
+ font-size: 9;
+ /* margin-top: 4; */
+ margin-right: 4;
+ margin-bottom: -3;
+ margin-left: 5;
+ margin-top: -1px;
+ }
+
.size-select {
width: 60px;
color: black;
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index b4778d3eb..0a1890ca4 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,27 +1,25 @@
-import { observable, runInAction, action, computed } from "mobx";
-import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { observer } from "mobx-react";
-import * as fa from '@fortawesome/free-solid-svg-icons';
-import { SelectionManager } from "./SelectionManager";
-import "./SettingsManager.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Networking } from "../Network";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from "../../Utils";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { ColorState, SketchPicker } from "react-color";
import { Doc } from "../../fields/Doc";
-import GroupManager from "./GroupManager";
-import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { BoolCast, StrCast } from "../../fields/Types";
+import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils";
+import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
import { DocServer } from "../DocServer";
-import { BoolCast, StrCast, NumCast } from "../../fields/Types";
+import { Networking } from "../Network";
+import { MainViewModal } from "../views/MainViewModal";
+import { CurrentUserUtils } from "./CurrentUserUtils";
+import { GroupManager } from "./GroupManager";
+import "./SettingsManager.scss";
import { undoBatch } from "./UndoManager";
-import { ColorState, SketchPicker } from "react-color";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@observer
-export default class SettingsManager extends React.Component<{}> {
+export class SettingsManager extends React.Component<{}> {
public static Instance: SettingsManager;
static _settingsStyle = addStyleSheet();
@observable private isOpen = false;
@@ -32,7 +30,7 @@ export default class SettingsManager extends React.Component<{}> {
@observable private new_password = "";
@observable private new_confirm = "";
- @computed get backgroundColor() { return Doc.UserDoc().defaultColor; }
+ @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
constructor(props: {}) {
super(props);
@@ -54,6 +52,7 @@ export default class SettingsManager extends React.Component<{}> {
}
@undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice");
+ @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value);
@undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value);
@undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value);
@undoBatch switchColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex));
@@ -86,17 +85,31 @@ export default class SettingsManager extends React.Component<{}> {
return <div className="preferences-content">
<div className="preferences-color">
- <div className="preferences-color-text">Background Color</div>
+ <div className="preferences-color-text">Back. Color</div>
{colorFlyout}
</div>
<div className="preferences-font">
- <div className="preferences-font-text">Default Font</div>
+ <div className="preferences-font-text">Font</div>
<select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
{fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
</select>
- <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}>
+ <select className="size-select" style={{ marginRight: "10px" }} onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}>
{fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
</select>
+ <div>
+ <div className="preferences-check">Show title</div>
+ <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = !Doc.UserDoc().showTitle} checked={BoolCast(Doc.UserDoc().showTitle)} />
+ </div>
+ <div>
+ <div className="preferences-check">Alt Buttons</div>
+ <input type="checkbox" onChange={e => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"]}
+ checked={BoolCast(Doc.UserDoc()["documentLinksButton-hideEnd"])} />
+ </div>
+ <div>
+ Autoscroll
+ <input type="checkbox" onChange={e => Doc.UserDoc()._noAutoscroll = !Doc.UserDoc()._noAutoscroll}
+ checked={!BoolCast(Doc.UserDoc()._noAutoscroll)} />
+ </div>
</div>
</div>;
}
@@ -158,10 +171,10 @@ export default class SettingsManager extends React.Component<{}> {
<div className="settings-title">Settings</div>
<div className="settings-username">{Doc.CurrentUserEmail}</div>
<button className="logout-button" onClick={() => window.location.assign(Utils.prepend("/logout"))} >
- {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
+ {CurrentUserUtils.GuestDashboard ? "Exit" : "Log Out"}
</button>
<div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} />
+ <FontAwesomeIcon icon={"times"} color="black" size={"lg"} />
</div>
</div>
<div className="settings-content">
@@ -180,6 +193,6 @@ export default class SettingsManager extends React.Component<{}> {
isDisplayed={this.isOpen}
interactive={true}
closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: "600px", height: "340px" }} />;
+ dialogueBoxStyle={{ width: "600px" }} />;
}
} \ No newline at end of file
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 7912db74d..b756716bf 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -62,6 +62,7 @@
input {
height: 10px;
+ cursor: pointer;
}
label {
@@ -69,11 +70,29 @@
font-style: italic;
}
}
+
+ .layoutDoc-acls {
+ display: flex;
+ flex-direction: column;
+ float: right;
+ margin-right: 12;
+ margin-top: -15;
+ align-items: center;
+
+ label {
+ font-weight: normal;
+ font-style: italic;
+ }
+
+ input {
+ cursor: pointer;
+ }
+ }
}
.main-container {
display: flex;
- margin-top: -10px;
+ margin-top: -25px;
.individual-container,
.group-container {
@@ -102,7 +121,7 @@
overflow-y: scroll;
overflow-x: hidden;
text-align: left;
- display: flex;
+ display: block;
align-content: center;
align-items: center;
text-align: center;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 48a3c023f..6fdbac41b 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,30 +1,26 @@
-import { observable, runInAction, action } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, AclAdmin, AclPrivate, DocListCast } from "../../fields/Doc";
-import { DocServer } from "../DocServer";
-import { Cast, StrCast } from "../../fields/Types";
+import Select from "react-select";
import * as RequestPromise from "request-promise";
+import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt, AclSym } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { Cast, StrCast } from "../../fields/Types";
+import { distributeAcls, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
import { Utils } from "../../Utils";
-import "./SharingManager.scss";
-import { observer } from "mobx-react";
-import * as fa from '@fortawesome/free-solid-svg-icons';
-import { DocumentView } from "../views/nodes/DocumentView";
-import { SelectionManager } from "./SelectionManager";
-import { DocumentManager } from "./DocumentManager";
+import { DocServer } from "../DocServer";
import { CollectionView } from "../views/collections/CollectionView";
import { DictationOverlay } from "../views/DictationOverlay";
-import GroupManager, { UserOptions } from "./GroupManager";
-import GroupMemberView from "./GroupMemberView";
-import Select from "react-select";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { List } from "../../fields/List";
-import { distributeAcls, SharingPermissions, GetEffectiveAcl } from "../../fields/util";
+import { MainViewModal } from "../views/MainViewModal";
+import { DocumentView } from "../views/nodes/DocumentView";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { library } from "@fortawesome/fontawesome-svg-core";
-
-library.add(fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown);
-
+import { DocumentManager } from "./DocumentManager";
+import { GroupManager, UserOptions } from "./GroupManager";
+import { GroupMemberView } from "./GroupMemberView";
+import "./SharingManager.scss";
+import { SelectionManager } from "./SelectionManager";
+import { intersection } from "lodash";
export interface User {
email: string;
@@ -47,6 +43,8 @@ interface GroupedOptions {
const indType = "!indType/";
const groupType = "!groupType/";
+const storage = "data";
+
/**
* A user who also has a notificationDoc.
*/
@@ -55,10 +53,9 @@ interface ValidatedUser {
notificationDoc: Doc;
}
-const storage = "data";
@observer
-export default class SharingManager extends React.Component<{}> {
+export class SharingManager extends React.Component<{}> {
public static Instance: SharingManager;
@observable private isOpen = false; // whether the SharingManager modal is open or not
@observable private users: ValidatedUser[] = []; // the list of users with notificationDocs
@@ -75,29 +72,28 @@ export default class SharingManager extends React.Component<{}> {
// if both showUserOptions and showGroupOptions are false then both are displayed
@observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component)
@observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
- private populating: boolean = false;
+ private populating: boolean = false; // whether the list of users is populating or not
+ @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
// private get linkVisible() {
// return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
// }
- public open = (target: DocumentView) => {
+ public open = (target?: DocumentView, target_doc?: Doc) => {
runInAction(() => this.users = []);
- // SelectionManager.DeselectAll();
this.populateUsers();
runInAction(() => {
this.targetDocView = target;
- this.targetDoc = target.props.Document;
+ this.targetDoc = target_doc || target?.props.Document;
DictationOverlay.Instance.hasActiveModal = true;
- this.isOpen = true;
+ this.isOpen = this.targetDoc !== undefined;
this.permissions = SharingPermissions.Edit;
});
- this.targetDoc!.author === Doc.CurrentUserEmail && !this.targetDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, this.targetDoc!);
}
public close = action(() => {
this.isOpen = false;
- this.selectedUsers = null; // resets the list of users and seleected users (in the react-select component)
+ this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
TaskCompletionBox.taskCompleted = false;
setTimeout(action(() => {
// this.copied = false;
@@ -119,7 +115,7 @@ export default class SharingManager extends React.Component<{}> {
}
/**
- * Populates the list of validated users (this.users) by adding registered users which have a sidebar-sharing.
+ * Populates the list of validated users (this.users) by adding registered users which have a mySharedDocs.
*/
populateUsers = async () => {
if (!this.populating) {
@@ -132,7 +128,7 @@ export default class SharingManager extends React.Component<{}> {
if (isCandidate) {
const userDocument = await DocServer.GetRefField(user.userDocumentId);
if (userDocument instanceof Doc) {
- const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc);
+ const notificationDoc = await Cast(userDocument.mySharedDocs, Doc);
runInAction(() => {
if (notificationDoc instanceof Doc) {
this.users.push({ user, notificationDoc });
@@ -151,20 +147,29 @@ export default class SharingManager extends React.Component<{}> {
* @param permission
*/
setInternalGroupSharing = (group: Doc, permission: string, targetDoc?: Doc) => {
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
const target = targetDoc || this.targetDoc!;
- const ACL = `ACL-${StrCast(group.groupName)}`;
+ const key = StrCast(group.groupName).replace(".", "_");
+ const ACL = `ACL-${key}`;
+
+ const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document);
+
+ docs.forEach(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
+ GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc);
- target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target);
+ if (key !== "Public") {
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
- // if documents have been shared, add the target to that list if it doesn't already exist, otherwise create a new list with the target
- group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
+ // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc
+ group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]);
- users.forEach(({ notificationDoc }) => {
- if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); // add the target to the notificationDoc if it hasn't already been added
- else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); // remove the target from the list if it already exists
+ users.forEach(({ user, notificationDoc }) => {
+ if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); // add the doc to the notificationDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists
+ });
+ }
});
}
@@ -175,17 +180,25 @@ export default class SharingManager extends React.Component<{}> {
*/
shareWithAddedMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
-
if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
}
/**
* Called from the properties sidebar to change permissions of a user.
*/
- shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, target: Doc) => {
- const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith));
- if (user) this.setInternalSharing(user, permission, target);
- else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, target);
+ shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
+ if (shareWith !== "Public") {
+ const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith));
+ docs.forEach(doc => {
+ if (user) this.setInternalSharing(user, permission, doc);
+ else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc);
+ });
+ }
+ else {
+ docs.forEach(doc => {
+ if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("ACL-Public", permission, doc);
+ });
+ }
}
/**
@@ -231,14 +244,16 @@ export default class SharingManager extends React.Component<{}> {
const key = user.email.replace('.', '_');
const ACL = `ACL-${key}`;
- target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target);
- if (permission !== SharingPermissions.None) {
- Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
- }
- else {
- Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
- }
+ const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document);
+
+ docs.forEach(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc);
+ GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc);
+
+ if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc);
+ else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc));
+ });
}
@@ -268,8 +283,10 @@ export default class SharingManager extends React.Component<{}> {
/**
* Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
*/
- private get sharingOptions() {
- return Object.values(SharingPermissions).map(permission =>
+ private sharingOptions(uniform: boolean) {
+ const dropdownValues: string[] = Object.values(SharingPermissions);
+ if (!uniform) dropdownValues.unshift("-multiple-");
+ return dropdownValues.map(permission =>
(
<option key={permission} value={permission}>
{permission}
@@ -280,27 +297,30 @@ export default class SharingManager extends React.Component<{}> {
private focusOn = (contents: string) => {
const title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
+ const docs = SelectionManager.SelectedDocuments().length > 1 ? SelectionManager.SelectedDocuments().map(docView => docView.props.Document) : [this.targetDoc];
return (
<span
className={"focus-span"}
title={title}
onClick={() => {
let context: Opt<CollectionView>;
- if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) {
+ if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
}
}}
onPointerEnter={action(() => {
- if (this.targetDoc) {
- Doc.BrushDoc(this.targetDoc);
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.BrushDoc(doc));
this.dialogueBoxOpacity = 0.1;
this.overlayOpacity = 0.1;
}
})}
onPointerLeave={action(() => {
- this.targetDoc && Doc.UnBrushDoc(this.targetDoc);
- this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+ this.dialogueBoxOpacity = 1;
+ this.overlayOpacity = 0.4;
+ }
})}
>
{contents}
@@ -372,8 +392,8 @@ export default class SharingManager extends React.Component<{}> {
* @returns the main interface of the SharingManager.
*/
private get sharingInterface() {
- const groupList = GroupManager.Instance?.getAllGroups() || [];
+ const groupList = GroupManager.Instance?.getAllGroups() || [];
const sortedUsers = this.users.slice().sort(this.sortUsers)
.map(({ user: { email } }) => ({ label: email, value: indType + email }));
const sortedGroups = groupList.slice().sort(this.sortGroups)
@@ -406,30 +426,42 @@ export default class SharingManager extends React.Component<{}> {
}
}
- const users = this.individualSort === "ascending" ? this.users.sort(this.sortUsers) : this.individualSort === "descending" ? this.users.sort(this.sortUsers).reverse() : this.users;
- const groups = this.groupSort === "ascending" ? groupList.sort(this.sortGroups) : this.groupSort === "descending" ? groupList.sort(this.sortGroups).reverse() : groupList;
+ const users = this.individualSort === "ascending" ? this.users.slice().sort(this.sortUsers) : this.individualSort === "descending" ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
+ const groups = this.groupSort === "ascending" ? groupList.slice().sort(this.sortGroups) : this.groupSort === "descending" ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
+
+ // handles the case where multiple documents are selected
+ const docs = SelectionManager.SelectedDocuments().length < 2 ?
+ [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]]
+ : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]);
+
+ const targetDoc = docs[0];
+
+ // tslint:disable-next-line: no-unnecessary-callback-wrapper
+ const admin = docs.map(doc => GetEffectiveAcl(doc)).every(acl => acl === AclAdmin); // if the user has admin access to all selected docs
- const effectiveAcl = this.targetDoc ? GetEffectiveAcl(this.targetDoc) : AclPrivate;
+ // users in common between all docs
+ const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
// the list of users shared with
- const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => {
- const userKey = user.email.replace('.', '_');
- const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`]);
+ const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`ACL-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc }) => {
+ const userKey = `ACL-${user.email.replace('.', '_')}`;
+ const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]);
+ const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-";
- return !permissions || user.email === this.targetDoc?.author ? null : (
+ return !permissions ? (null) : (
<div
key={userKey}
className={"container"}
>
<span className={"padding"}>{user.email}</span>
<div className="edit-actions">
- {effectiveAcl === AclAdmin ? (
+ {admin ? (
<select
className={"permissions-dropdown"}
value={permissions}
onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
>
- {this.sharingOptions}
+ {this.sharingOptions(uniform)}
</select>
) : (
<div className={"permissions-dropdown"}>
@@ -441,22 +473,26 @@ export default class SharingManager extends React.Component<{}> {
);
});
+ // checks if every doc has the same author
+ const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
+
// the owner of the doc and the current user are placed at the top of the user list.
userListContents.unshift(
- (
- <div
- key={"owner"}
- className={"container"}
- >
- <span className={"padding"}>{this.targetDoc?.author === Doc.CurrentUserEmail ? "Me" : this.targetDoc?.author}</span>
- <div className="edit-actions">
- <div className={"permissions-dropdown"}>
- Owner
+ sameAuthor ?
+ (
+ <div
+ key={"owner"}
+ className={"container"}
+ >
+ <span className={"padding"}>{targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author}</span>
+ <div className="edit-actions">
+ <div className={"permissions-dropdown"}>
+ Owner
+ </div>
</div>
</div>
- </div>
- ),
- this.targetDoc?.author !== Doc.CurrentUserEmail ?
+ ) : null,
+ sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ?
(
<div
key={"me"}
@@ -465,42 +501,54 @@ export default class SharingManager extends React.Component<{}> {
<span className={"padding"}>Me</span>
<div className="edit-actions">
<div className={"permissions-dropdown"}>
- {this.targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]}
+ {targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]}
</div>
</div>
</div>
) : null
);
+ // dummy doc for public acl
+ const publicDoc = new Doc;
+ publicDoc.groupName = "Public";
// the list of groups shared with
- const groupListContents = groups.map(group => {
- const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`]);
-
- return !permissions ? null : (
+ const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`ACL-${StrCast(groupName).replace('.', '_')}`) : true);
+ groupListMap.unshift(publicDoc);
+ const groupListContents = groupListMap.map(group => {
+ const groupKey = `ACL-${StrCast(group.groupName)}`;
+ const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
+ const permissions = uniform ? StrCast(targetDoc?.[`ACL-${StrCast(group.groupName)}`]) : "-multiple-";
+
+ return !permissions ? (null) : (
<div
- key={StrCast(group.groupName)}
+ key={groupKey}
className={"container"}
>
<div className={"padding"}>{group.groupName}</div>
- <div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
- <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
- </div>
+ {group.groupName !== "Public" ?
+ (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
+ <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+ </div>)
+ : (null)}
<div className="edit-actions">
- <select
- className={"permissions-dropdown"}
- value={permissions}
- onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
- >
- {this.sharingOptions}
- </select>
+ {admin ? (
+ <select
+ className={"permissions-dropdown"}
+ value={permissions}
+ onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
+ >
+ {this.sharingOptions(uniform)}
+ </select>
+ ) : (
+ <div className={"permissions-dropdown"}>
+ {permissions}
+ </div>
+ )}
</div>
</div>
);
});
- // don't display the group list if all groups are null
- const displayGroupList = !groupListContents?.every(group => group === null);
-
return (
<div className={"sharing-interface"}>
{GroupManager.Instance?.currentGroup ?
@@ -519,7 +567,7 @@ export default class SharingManager extends React.Component<{}> {
style={{ backgroundColor: this.copied ? "lawngreen" : "gainsboro" }}
onClick={this.copy}
>
- <FontAwesomeIcon icon={fa.faCopy} />
+ <FontAwesomeIcon icon={"copy"} />
</div>
</div>
}
@@ -540,7 +588,7 @@ export default class SharingManager extends React.Component<{}> {
</div>
<div className={"hr-substitute"} /> */}
<div className="sharing-contents">
- <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
+ <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p>
<div className={"close-button"} onClick={this.close}>
<FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
@@ -561,7 +609,7 @@ export default class SharingManager extends React.Component<{}> {
}}
/>
<select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
- {this.sharingOptions}
+ {this.sharingOptions(true)}
</select>
<button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
Share
@@ -571,6 +619,9 @@ export default class SharingManager extends React.Component<{}> {
<input type="checkbox" onChange={action(() => this.showUserOptions = !this.showUserOptions)} /> <label style={{ marginRight: 10 }}>Individuals</label>
<input type="checkbox" onChange={action(() => this.showGroupOptions = !this.showGroupOptions)} /> <label>Groups</label>
</div>
+ <div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label>
+ </div>
</div>
}
<div className="main-container">
@@ -578,11 +629,11 @@ export default class SharingManager extends React.Component<{}> {
<div
className="user-sort"
onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
- Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} />
- : this.individualSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} />
- : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />}
+ Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
+ : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
+ : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
</div>
- <div className={"users-list"} style={{ display: "block" }}>{/*200*/}
+ <div className={"users-list"}>
{userListContents}
</div>
</div>
@@ -590,23 +641,13 @@ export default class SharingManager extends React.Component<{}> {
<div
className="user-sort"
onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} />
- : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} />
- : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />}
+ Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
+ : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
+ : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
</div>
- <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/}
- {
- !displayGroupList ?
- <div
- className={"none"}
- >
- There are no groups this document has been shared with.
- </div>
- :
- groupListContents
- }
-
+ <div className={"groups-list"}>
+ {groupListContents}
</div>
</div>
</div>
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 68ccefcb5..5acb3e4c8 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,12 +1,14 @@
import React = require("react");
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
import "./AntimodeMenu.scss";
+export interface AntimodeMenuProps {
+}
/**
* This is an abstract class that serves as the base for a PDF-style or Marquee-style
* menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
*/
-export default abstract class AntimodeMenu extends React.Component {
+export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Component<T, {}> {
protected _offsetY: number = 0;
protected _offsetX: number = 0;
protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
@@ -18,7 +20,7 @@ export default abstract class AntimodeMenu extends React.Component {
@observable protected _transitionProperty: string = "opacity";
@observable protected _transitionDuration: string = "0.5s";
@observable protected _transitionDelay: string = "";
- @observable protected _canFade: boolean = true;
+ @observable protected _canFade: boolean = false;
@observable public Pinned: boolean = false;
@@ -84,7 +86,7 @@ export default abstract class AntimodeMenu extends React.Component {
@action
protected togglePin = (e: React.MouseEvent) => {
- this.Pinned = !this.Pinned;
+ runInAction(() => this.Pinned = !this.Pinned);
}
protected dragStart = (e: React.PointerEvent) => {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 07f7b8e6d..952100cb0 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -3,14 +3,9 @@ import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextM
import { observable, action, computed, runInAction, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import "./ContextMenu.scss";
-import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
import Measure from "react-measure";
-library.add(faSearch);
-library.add(faCircle);
-
@observer
export class ContextMenu extends React.Component {
static Instance: ContextMenu;
@@ -100,7 +95,7 @@ export class ContextMenu extends React.Component {
}
@action
moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
- if (this.findByDescription(after.description)) {
+ if (after && this.findByDescription(after.description)) {
const curInd = this._items.findIndex((i) => i.description === item.description);
this._items.splice(curInd, 1);
const afterInd = this._items.findIndex((i) => i.description === after.description);
@@ -220,7 +215,7 @@ export class ContextMenu extends React.Component {
@computed get menuItems() {
if (!this._searchString) {
- return this._items.map(item => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} />);
+ return this._items.map(item => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} pageX={this.pageX} />);
}
return this.filteredViews;
}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 7e233ec04..eddecb7a7 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,12 +1,10 @@
import React = require("react");
import { observable, action } from "mobx";
import { observer } from "mobx-react";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { UndoManager } from "../util/UndoManager";
-
-library.add(faAngleRight);
+import { NumberLiteralType } from "typescript";
export interface OriginalMenuProps {
description: string;
@@ -31,6 +29,7 @@ export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
export class ContextMenuItem extends React.Component<ContextMenuProps & { selected?: boolean }> {
@observable private _items: Array<ContextMenuProps> = [];
@observable private overItem = false;
+ @observable private subRef = React.createRef<HTMLDivElement>();
constructor(props: ContextMenuProps | SubmenuProps) {
super(props);
@@ -54,6 +53,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
currentTimeout?: any;
static readonly timeout = 300;
_overPosY = 0;
+ _overPosX = 0;
onPointerEnter = (e: React.MouseEvent) => {
if (this.currentTimeout) {
clearTimeout(this.currentTimeout);
@@ -63,6 +63,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
return;
}
this._overPosY = e.clientY;
+ this._overPosX = e.clientX;
this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
}
@@ -78,6 +79,9 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
}
render() {
+
+
+
if ("event" in this.props) {
return (
<div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}>
@@ -94,8 +98,13 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
} else if ("subitems" in this.props) {
const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center";
const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : "";
+
+ // here
const submenu = !this.overItem ? (null) :
- <div className="contextMenu-subMenu-cont" style={{ marginLeft: "90%", left: "0px", marginTop }}>
+ <div className="contextMenu-subMenu-cont"
+ style={{
+ marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? "90%" : "20%", marginTop
+ }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
if (!("noexpand" in this.props)) {
@@ -115,7 +124,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
<div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
style={{ alignItems: "center" }} >
{this.props.description}
- <FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px" }} />
+ <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
</div>
{submenu}
</div>
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 9ed14509f..f4f96da8a 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -4,7 +4,7 @@ import "normalize.css";
import * as React from 'react';
import { DictationManager } from '../util/DictationManager';
import "./Main.scss";
-import MainViewModal from './MainViewModal';
+import { MainViewModal } from './MainViewModal';
@observer
export class DictationOverlay extends React.Component {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 8c8bb6fde..aa888b34a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -58,7 +58,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
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);// && !Doc.SelectedTool(); // bcz: inking state shouldn't affect static tools
+ active = (outsideReaction?: boolean) => !this.props.Document._isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);// && !Doc.SelectedTool(); // bcz: inking state shouldn't affect static tools
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
@@ -131,11 +131,10 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const toRemove = value.filter(v => docs.includes(v));
if (toRemove.length !== 0) {
- const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
+ const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
toRemove.forEach(doc => {
Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey + "-annotations", doc);
recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
- doc.deleted = true;
});
return true;
}
@@ -179,8 +178,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
added.map(doc => doc.context = this.props.Document);
(targetDataDoc[this.annotationKey] as List<Doc>).push(...added);
targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
- const lastModified = "lastModified";
- targetDataDoc[lastModified] = new DateField(new Date(Date.now()));
}
}
}
@@ -188,9 +185,9 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
}
whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
- active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.props.Document.isBackground) &&
+ active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.props.Document._) &&
(this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0 || BoolCast((this.layoutDoc as any).forceActive)) ? true : false)
- annotationsActive = (outsideReaction?: boolean) => (Doc.GetSelectedTool() !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
+ annotationsActive = (outsideReaction?: boolean) => (Doc.GetSelectedTool() !== InkTool.None || (this.props.Document._isBackground && this.props.active()) ||
(this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
}
return Component;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 8748b1880..cf2bd5176 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,45 +1,34 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from '@material-ui/core';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc } from "../../fields/Doc";
import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
import { emptyFunction, setupMoveUpEvents } from "../../Utils";
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DragManager } from '../util/DragManager';
-import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
-import { ParentDocSelector } from './collections/ParentDocumentSelector';
-import './collections/ParentDocumentSelector.scss';
+import { SelectionManager } from '../util/SelectionManager';
+import { SharingManager } from '../util/SharingManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { TabDocView } from './collections/TabDocView';
import './DocumentButtonBar.scss';
import { MetadataEntryMenu } from './MetadataEntryMenu';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
-import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { Tooltip } from '@material-ui/core';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-library.add(faLink);
-library.add(faTag);
-library.add(faTimes);
-library.add(faArrowAltCircleDown);
-library.add(faArrowAltCircleUp);
-library.add(faArrowAltCircleRight);
-library.add(faStopCircle);
-library.add(faCheckCircle);
-library.add(faCloudUploadAlt);
-library.add(faSyncAlt);
-library.add(faShare);
-library.add(faPhotoVideo);
-
const cloud: IconProp = "cloud-upload-alt";
const fetch: IconProp = "sync-alt";
@@ -164,11 +153,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
e.preventDefault();
let googleDoc = await Cast(dataDoc.googleDoc, Doc);
if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, useCors: false };
googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
dataDoc.googleDoc = googleDoc;
}
- CollectionDockingView.AddRightSplit(googleDoc);
+ CollectionDockingView.AddSplit(googleDoc, "right");
} else if (e.altKey) {
e.preventDefault();
window.open(googleDocUrl);
@@ -195,17 +184,57 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get pinButton() {
const targetDoc = this.view0?.props.Document;
- const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
+ let isPinned = targetDoc && Doc.isDocPinned(targetDoc);
+ // More than 1 document selected then all must be in presentation for isPinned to be true (then it will unpin all)
+ if (SelectionManager.SelectedDocuments().length > 1) {
+ SelectionManager.SelectedDocuments().forEach((docView: DocumentView) => {
+ if (Doc.isDocPinned(docView.props.Document)) isPinned = true;
+ else isPinned = false;
+ });
+ }
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}>
<div className="documentButtonBar-linker"
style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white", border: isPinned ? "black 1px solid " : "" }}
- onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
+ onClick={e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, isPinned))}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
/>
</div></Tooltip>;
}
@computed
+ get shareButton() {
+ const targetDoc = this.view0?.props.Document;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Sharing Manager"}</div></>}>
+ <div className="documentButtonBar-linker" style={{ color: "white" }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="users"
+ />
+ </div></Tooltip >;
+ }
+
+ @computed
+ get annotateButton() {
+ const targetDoc = this.view0?.props.Document;
+ const isAnnotating = targetDoc?.isAnnotating;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${isAnnotating ? "Exit" : "Enter"} annotation mode`}</div></>}>
+ <div className="documentButtonBar-linker" style={{ backgroundColor: isAnnotating ? "white" : "", color: isAnnotating ? "black" : "white", }}
+ onClick={e => targetDoc.isAnnotating = !targetDoc.isAnnotating}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="edit"
+ />
+ </div></Tooltip >;
+ }
+
+ @computed
+ get moreButton() {
+ const targetDoc = this.view0?.props.Document;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Properties Panel"}</div></>}>
+ <div className="documentButtonBar-linker" style={{ color: "white" }} onClick={action(e =>
+ CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="ellipsis-h"
+ />
+ </div></Tooltip >;
+ }
+
+ @computed
get metadataButton() {
const view0 = this.view0;
return !view0 ? (null) : <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}>
@@ -218,17 +247,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
</Flyout>
</div></Tooltip>;
}
-
- @computed
- get contextButton() {
- return !this.view0 ? (null) : <ParentDocSelector Document={this.view0.props.Document} addDocTab={(doc, where) => {
- where === "onRight" ? CollectionDockingView.AddRightSplit(doc) :
- this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc) :
- this.view0?.props.addDocTab(doc, "onRight");
- return true;
- }} />;
- }
-
@observable _aliasDown = false;
onAliasButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
@@ -293,12 +311,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div className="documentButtonBar-button">
{this.pinButton}
</div>
- {/* <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
+ {!Doc.UserDoc()["documentLinksButton-hideEnd"] ? (null) : <div className="documentButtonBar-button">
+ {this.shareButton}
+ </div>}
+ {![DocumentType.VID, DocumentType.WEB].includes(StrCast(this.view0.props.Document.type) as DocumentType) ? (null) : <div className="documentButtonBar-button">
+ {this.annotateButton}
+ </div>}
+ <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
{this.considerGoogleDocsPush}
</div>
<div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
{this.considerGoogleDocsPull}
- </div> */}
+ </div>
+ <div className="documentButtonBar-button">
+ {this.moreButton}
+ </div>
</div>;
}
}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 1e8cfdff4..e02408559 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -16,22 +16,12 @@ $linkGap : 3px;
grid-template-columns: 8px 16px 1fr 8px 8px;
pointer-events: none;
- #documentDecorations-centerCont {
+ .documentDecorations-centerCont {
grid-column: 3;
background: none;
}
- .documentDecorations-resizer {
- pointer-events: auto;
- background: $alt-accent;
- opacity: 0.1;
- }
-
- .documentDecorations-resizer:hover {
- opacity: 1;
- }
-
- .documentDecorations-selector {
+ .documentDecorations-levelSelector {
pointer-events: auto;
height: 15px;
width: 15px;
@@ -49,27 +39,43 @@ $linkGap : 3px;
grid-row: 4;
}
- #documentDecorations-topLeftResizer,
- #documentDecorations-leftResizer,
- #documentDecorations-bottomLeftResizer {
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-topRightResizer,
+ .documentDecorations-bottomLeftResizer,
+ .documentDecorations-bottomRightResizer,
+ .documentDecorations-leftResizer,
+ .documentDecorations-topResizer,
+ .documentDecorations-bottomResizer,
+ .documentDecorations-rightResizer {
+ pointer-events: auto;
+ background: $alt-accent;
+ opacity: 0.1;
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-leftResizer,
+ .documentDecorations-bottomLeftResizer {
grid-column: 1
}
- #documentDecorations-topResizer,
- #documentDecorations-bottomResizer {
+ .documentDecorations-topResizer,
+ .documentDecorations-bottomResizer {
grid-column-start: 2;
grid-column-end: 5;
}
- #documentDecorations-bottomRightResizer,
- #documentDecorations-topRightResizer,
- #documentDecorations-rightResizer {
+ .documentDecorations-bottomRightResizer,
+ .documentDecorations-topRightResizer,
+ .documentDecorations-rightResizer {
grid-column-start: 5;
grid-column-end: 7;
}
- #documentDecorations-rotation,
- #documentDecorations-borderRadius {
+ .documentDecorations-rotation,
+ .documentDecorations-borderRadius {
grid-column: 5;
grid-row: 4;
border-radius: 100%;
@@ -79,6 +85,7 @@ $linkGap : 3px;
top: 12;
position: relative;
pointer-events: all;
+ cursor: grab;
.borderRadiusTooltip {
width: 10px;
@@ -86,69 +93,69 @@ $linkGap : 3px;
position: absolute;
}
}
- #documentDecorations-rotation {
+ .documentDecorations-rotation {
background: transparent;
right: -15;
}
- #documentDecorations-topLeftResizer,
- #documentDecorations-bottomRightResizer {
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-bottomRightResizer {
cursor: nwse-resize;
background: unset;
opacity: 1;
}
- #documentDecorations-topLeftResizer {
+ .documentDecorations-topLeftResizer {
border-left: 2px solid;
border-top: solid 2px;
}
- #documentDecorations-bottomRightResizer {
+ .documentDecorations-bottomRightResizer {
border-right: 2px solid;
border-bottom: solid 2px;
}
- #documentDecorations-topLeftResizer:hover,
- #documentDecorations-bottomRightResizer:hover {
+ .documentDecorations-topLeftResizer:hover,
+ .documentDecorations-bottomRightResizer:hover {
opacity: 1;
}
- #documentDecorations-bottomRightResizer {
+ .documentDecorations-bottomRightResizer {
grid-row: 4;
}
- #documentDecorations-topRightResizer,
- #documentDecorations-bottomLeftResizer {
+ .documentDecorations-topRightResizer,
+ .documentDecorations-bottomLeftResizer {
cursor: nesw-resize;
background: unset;
opacity: 1;
}
- #documentDecorations-topRightResizer {
+ .documentDecorations-topRightResizer {
border-right: 2px solid;
border-top: 2px solid;
}
- #documentDecorations-bottomLeftResizer {
+ .documentDecorations-bottomLeftResizer {
border-left: 2px solid;
border-bottom: 2px solid;
}
- #documentDecorations-topRightResizer:hover,
- #documentDecorations-bottomLeftResizer:hover {
+ .documentDecorations-topRightResizer:hover,
+ .documentDecorations-bottomLeftResizer:hover {
cursor: nesw-resize;
background: dimGray;
opacity: 1;
}
- #documentDecorations-topResizer,
- #documentDecorations-bottomResizer {
+ .documentDecorations-topResizer,
+ .documentDecorations-bottomResizer {
cursor: ns-resize;
}
- #documentDecorations-leftResizer,
- #documentDecorations-rightResizer {
+ .documentDecorations-leftResizer,
+ .documentDecorations-rightResizer {
cursor: ew-resize;
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 03746a1d2..81c1676b0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,56 +1,30 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faTextHeight, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes, faAngleLeft, faAngleRight, faAngleDoubleLeft, faAngleDoubleRight, faPause, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction, get } from "mobx";
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DataSym, Field, WidthSym, HeightSym, AclEdit, AclAdmin } from "../../fields/Doc";
+import { AclAdmin, AclEdit, DataSym, Doc, Field, WidthSym, HeightSym } from "../../fields/Doc";
import { Document } from '../../fields/documentSchemas';
+import { HtmlField } from '../../fields/HtmlField';
+import { InkField } from "../../fields/InkField";
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 { Cast, NumCast } from "../../fields/Types";
+import { GetEffectiveAcl } from '../../fields/util';
+import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from "../../Utils";
+import { DocUtils, Docs } from "../documents/Documents";
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
+import { SnappingManager } from '../util/SnappingManager';
import { undoBatch, UndoManager } from "../util/UndoManager";
+import { CollectionDockingView } from './collections/CollectionDockingView';
import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { DocumentView } from "./nodes/DocumentView";
import React = require("react");
import e = require('express');
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { SnappingManager } from '../util/SnappingManager';
-import { HtmlField } from '../../fields/HtmlField';
-import { InkField } from "../../fields/InkField";
-import { Tooltip } from '@material-ui/core';
-import { GetEffectiveAcl } from '../../fields/util';
-import { DocumentIcon } from './nodes/DocumentIcon';
-import { render } from 'react-dom';
-import { createLessThan } from 'typescript';
-import FormatShapePane from './collections/collectionFreeForm/FormatShapePane';
-import { PropertiesView } from './collections/collectionFreeForm/PropertiesView';
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm, faTextHeight);
-library.add(faLink);
-library.add(faTag);
-library.add(faTimes);
-library.add(faArrowAltCircleDown);
-library.add(faArrowAltCircleUp);
-library.add(faStopCircle);
-library.add(faCheckCircle);
-library.add(faCloudUploadAlt);
-library.add(faSyncAlt);
-library.add(faShare);
-library.add(faAngleDoubleLeft);
-library.add(faAngleDoubleRight);
-library.add(faAngleLeft);
-library.add(faAngleRight);
-library.add(faPause);
-library.add(faExternalLinkAlt);
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { InkStrokeProperties } from './InkStrokeProperties';
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -121,7 +95,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith("#")) {
const selectionTitleFieldKey = this._titleControlString.substring(1);
- selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-"));
+ selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"));
UndoManager.RunInBatch(() => selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d => {
const value = typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle;
Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, value, true);
@@ -212,8 +186,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (e.button === 0) {
const selectedDocs = SelectionManager.SelectedDocuments();
if (selectedDocs.length) {
- //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
- CollectionDockingView.AddRightSplit(Doc.MakeAlias(selectedDocs[0].props.Document), selectedDocs[0].props.LibraryPath);
+ if (e.ctrlKey) {
+ const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+ alias.context = undefined;
+ //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
+ CollectionDockingView.AddSplit(alias, "right");
+ } else if (e.shiftKey) {
+ const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+ alias.context = undefined;
+ alias.x = -alias[WidthSym]() / 2;
+ alias.y = -alias[HeightSym]() / 2;
+ //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
+ } else {
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
+ }
}
}
SelectionManager.DeselectAll();
@@ -310,7 +297,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const newY = Math.sin(angle) * (ink.X - this._centerPoints[index].X) + Math.cos(angle) * (ink.Y - this._centerPoints[index].Y) + this._centerPoints[index].Y;
newPoints.push({ X: newX, Y: newY });
}
- doc.data = new InkField(newPoints);
+ Doc.GetProto(doc).data = new InkField(newPoints);
const xs = newPoints.map(p => p.X);
const ys = newPoints.map(p => p.Y);
const left = Math.min(...xs);
@@ -349,7 +336,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const doc = Document(element.rootDoc);
if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height) {
this._inkDocs.push({ x: doc.x, y: doc.y, width: doc._width, height: doc._height });
- if (FormatShapePane.Instance._lock) {
+ if (InkStrokeProperties.Instance?._lock) {
doc._nativeHeight = doc._height;
doc._nativeWidth = doc._width;
}
@@ -358,13 +345,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
if (e.button === 0) {
- this._resizeHdlId = e.currentTarget.id;
+ this._resizeHdlId = e.currentTarget.className;
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?.();
+ SelectionManager.SelectedDocuments()[0].props.setupDragLines?.(e.ctrlKey || e.shiftKey);
}
this._snapX = e.pageX;
this._snapY = e.pageY;
@@ -382,7 +369,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
var fixedAspect = first.layoutDoc._nativeWidth ? NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight) : 0;
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
const doc = Document(element.rootDoc);
- if (doc.type === DocumentType.INK && doc._width && doc._height && FormatShapePane.Instance._lock) {
+ if (doc.type === DocumentType.INK && doc._width && doc._height && InkStrokeProperties.Instance?._lock) {
fixedAspect = NumCast(doc._nativeWidth) / NumCast(doc._nativeHeight);
}
}));
@@ -492,10 +479,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
doc[DataSym][fieldKey + "-nativeHeight"] = doc._nativeHeight = nheight = doc._height || 0;
}
const anno = Cast(doc.annotationOn, Doc, null);
- if (e.ctrlKey && anno) {
+ if (e.ctrlKey && (anno || doc.type === DocumentType.IMG)) {
dW !== 0 && runInAction(() => {
- const dataDoc = anno[DataSym];
- const annoFieldKey = Doc.LayoutFieldKey(anno);
+ const dataDoc = (anno ?? doc)[DataSym];
+ const annoFieldKey = Doc.LayoutFieldKey(anno ?? doc);
const nw = NumCast(dataDoc[annoFieldKey + "-nativeWidth"]);
const nh = NumCast(dataDoc[annoFieldKey + "-nativeHeight"]);
dataDoc[annoFieldKey + "-nativeWidth"] = nw + (dW > 0 ? 10 : -10);
@@ -558,7 +545,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const newY = ((doc.y || 0) - this._inkDocs[index].y) + (i.Y * (doc._height || 0)) / this._inkDocs[index].height;
newPoints.push({ X: newX, Y: newY });
});
- doc.data = new InkField(newPoints);
+ Doc.GetProto(doc).data = new InkField(newPoints);
}
doc._nativeWidth = 0;
@@ -591,19 +578,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
}
public static DocumentIcon(layout: string) {
- const button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
- layout.indexOf("ImageBox") !== -1 ? faImage :
- layout.indexOf("Formatted") !== -1 ? faStickyNote :
- layout.indexOf("Video") !== -1 ? faFilm :
- layout.indexOf("Collection") !== -1 ? faObjectGroup :
- faCaretUp;
+ const button = layout.indexOf("PDFBox") !== -1 ? "file-pdf" :
+ layout.indexOf("ImageBox") !== -1 ? "image" :
+ layout.indexOf("Formatted") !== -1 ? "sticky-note" :
+ layout.indexOf("Video") !== -1 ? "film" :
+ layout.indexOf("Collection") !== -1 ? "object-group" :
+ "caret-up";
return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
}
render() {
- const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined;
+ const darkScheme = CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : undefined;
const bounds = this.Bounds;
const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
- 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)) {
+ if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return (null);
}
const canDelete = SelectionManager.SelectedDocuments().some(docView => {
@@ -611,39 +598,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return collectionAcl === AclAdmin || collectionAcl === AclEdit;
});
const minimal = bounds.r - bounds.x < 100 ? true : false;
- const maximizeIcon = minimal ? (
- <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top">
- <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}>
- <FontAwesomeIcon size="lg" icon="cog" />
- </div></Tooltip>) : canDelete ? (
- <Tooltip title={<><div className="dash-tooltip">Close</div></>} placement="top">
- <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
- {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
- <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
- </div></Tooltip>) : (null);
+ const closeIcon = canDelete ? (
+ <Tooltip title={<div className="dash-tooltip">Close</div>} placement="top">
+ <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
+ <FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" />
+ </div></Tooltip>) : (null);
const titleArea = this._edtingTitle ?
+ <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
+ onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> :
<>
- <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
- onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} />
- {minimal ? (null) : <div className="publishBox" // title="make document referenceable by its title"
- // onPointerDown={action(e => {
- // if (!seldoc.props.Document.customTitle) {
- // seldoc.props.Document.customTitle = true;
- // StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
- // this._accumulatedTitle = StrCast(seldoc.props.Document.title);
- // }
- // DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument);
- // })}
- >
- {/* <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon> */}
- </div>}
- </> :
- <>
- {minimal ? (null) : <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
- <FontAwesomeIcon size="lg" icon="cog" />
+ {minimal ? (null) : <Tooltip title={<div className="dash-tooltip">Show context menu</div>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
+ <FontAwesomeIcon size="lg" icon="bars" />
</div></Tooltip>}
- <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
+ <div className="documentDecorations-title" style={{ gridColumnEnd: minimal ? 4 : 5, gridColumnStart: minimal ? 2 : 3 }} key="title" onPointerDown={this.onTitleDown} >
<span style={{ width: "100%", display: "inline-block", cursor: "move" }}>{`${this.selectionTitle}`}</span>
</div>
</>;
@@ -659,8 +627,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (bounds.y > bounds.b) {
bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight);
}
- var offset = 0;
- let useRotation = seldoc.rootDoc.type === DocumentType.INK;
+ const useRotation = seldoc.rootDoc.type === DocumentType.INK;
return (<div className="documentDecorations" style={{ background: darkScheme }} >
<div className="documentDecorations-background" style={{
@@ -674,45 +641,37 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
<div className="documentDecorations-container" key="container" ref={this.setTextBar} style={{
- width: (bounds.r - bounds.x + this._resizeBorderWidth + offset) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + offset) + "px",
- left: bounds.x - this._resizeBorderWidth / 2 - offset / 2,
- top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight - offset / 2,
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
}}>
- {maximizeIcon}
+ {closeIcon}
{titleArea}
- {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
- <Tooltip title={<><div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></>} placement="top">
+ {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK || minimal ? (null) :
+ <Tooltip title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
<div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
<FontAwesomeIcon icon={seldoc.finalLayoutKey.includes("icon") ? "window-restore" : "window-minimize"} className="documentView-minimizedIcon" />
</div></Tooltip>}
- <Tooltip title={<><div className="dash-tooltip">Open In a New Pane</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}>
+ <Tooltip title={<div className="dash-tooltip">Open In a New Pane</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
{SelectionManager.SelectedDocuments().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."}
</div></Tooltip>
- <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-leftResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-centerCont"></div>
- <div id="documentDecorations-rightResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-centerCont"></div>
+ <div className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
{seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- <Tooltip title={<><div className="dash-tooltip">tap to select containing document</div></>} placement="top">
- <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+ <Tooltip title={<div className="dash-tooltip">tap to select containing document</div>} placement="top">
+ <div className="documentDecorations-levelSelector"
onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
- <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+ <FontAwesomeIcon className="documentdecorations-times" icon={"arrow-alt-circle-up"} size="lg" />
</div></Tooltip>}
- <div id={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
+ <div className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div>
</div >
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index ec3e754fb..1b4b9a2be 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -4,10 +4,8 @@ import { observer } from 'mobx-react';
import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
-import "./EditableView.scss";
import { DragManager } from '../util/DragManager';
-import { ComputedField } from '../../fields/ScriptField';
-import { FieldValue } from '../../fields/Types';
+import "./EditableView.scss";
export interface EditableProps {
/**
@@ -54,10 +52,6 @@ export interface EditableProps {
color?: string | undefined;
onDrop?: any;
placeholder?: string;
- highlight?: boolean;
- positions?: number[];
- search?: string;
- bing?: () => string | undefined;
}
/**
@@ -96,25 +90,30 @@ export class EditableView extends React.Component<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Tab") {
- e.stopPropagation();
- this.finalizeEdit(e.currentTarget.value, e.shiftKey, false);
- this.props.OnTab && this.props.OnTab(e.shiftKey);
- } else if (e.key === "Enter") {
- e.stopPropagation();
- if (!e.ctrlKey) {
+ switch (e.key) {
+ case "Tab":
+ e.stopPropagation();
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false);
- } else if (this.props.OnFillDown) {
- this.props.OnFillDown(e.currentTarget.value);
+ this.props.OnTab && this.props.OnTab(e.shiftKey);
+ break;
+ case "Enter":
+ e.stopPropagation();
+ if (!e.ctrlKey) {
+ this.finalizeEdit(e.currentTarget.value, e.shiftKey, false);
+ } else if (this.props.OnFillDown) {
+ this.props.OnFillDown(e.currentTarget.value);
+ this._editing = false;
+ this.props.isEditingCallback?.(false);
+ }
+ break;
+ case "Escape":
+ e.stopPropagation();
this._editing = false;
this.props.isEditingCallback?.(false);
- }
- } else if (e.key === "Escape") {
- e.stopPropagation();
- this._editing = false;
- this.props.isEditingCallback?.(false);
- } else if (e.key === ":") {
- this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
+ break;
+ case ":":
+ this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
+ break;
}
}
@@ -123,11 +122,9 @@ export class EditableView extends React.Component<EditableProps> {
e.nativeEvent.stopPropagation();
if (this._ref.current && this.props.showMenuOnLoad) {
this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
- } else {
- if (!this.props.onClick?.(e)) {
- this._editing = true;
- this.props.isEditingCallback?.(true);
- }
+ } else if (!this.props.onClick?.(e)) {
+ this._editing = true;
+ this.props.isEditingCallback?.(true);
}
e.stopPropagation();
}
@@ -154,7 +151,7 @@ export class EditableView extends React.Component<EditableProps> {
@action
setIsFocused = (value: boolean) => {
const wasFocused = this._editing;
- //this._editing = value;
+ this._editing = value;
return wasFocused !== this._editing;
}
@@ -171,6 +168,7 @@ export class EditableView extends React.Component<EditableProps> {
onPointerDown: this.stopPropagation,
onClick: this.stopPropagation,
onPointerUp: this.stopPropagation,
+ onKeyPress: this.stopPropagation,
value: this.props.autosuggestProps.value,
onChange: this.props.autosuggestProps.onChange
}}
@@ -179,6 +177,7 @@ export class EditableView extends React.Component<EditableProps> {
defaultValue={this.props.GetValue()}
onKeyDown={this.onKeyDown}
autoFocus={true}
+ onKeyPress={e => e.stopPropagation()}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)}
onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
style={{ display: this.props.display, fontSize: this.props.fontSize, minWidth: 20 }}
@@ -186,51 +185,25 @@ export class EditableView extends React.Component<EditableProps> {
/>;
}
- returnHighlights() {
- const results = [];
- const contents = this.props.bing!();
-
- if (contents !== undefined) {
- if (this.props.positions !== undefined) {
- const positions = this.props.positions;
- const length = this.props.search!.length;
-
- // contents = String(this.props.contents.valueOf());
-
- results.push(<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(0, this.props.positions[0]) : this.props.placeholder?.valueOf()}</span>);
- positions.forEach((num, cur) => {
- results.push(<span style={{ backgroundColor: "#FFFF00", fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(num, num + length) : this.props.placeholder?.valueOf()}</span>);
- let end = 0;
- cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1];
- results.push(<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(num + length, end) : this.props.placeholder?.valueOf()}</span>);
- }
- );
- }
- return results;
- }
- else {
- return <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>;
- }
- }
-
render() {
if (this._editing && this.props.GetValue() !== undefined) {
return this.props.sizeToContent ?
<div style={{ display: "grid", minWidth: 100 }}>
- <div style={{ display: "inline-block", position: "relative", height: 0, width: "100%", overflow: "hidden" }}>{this.props.GetValue()}</div>
+ <div style={{ display: "inline-block", position: "relative", height: 0, width: "100%", overflow: "hidden" }}>
+ {this.props.GetValue()}
+ </div>
{this.renderEditor()}
- </div> : this.renderEditor();
- } else {
- setTimeout(() => this.props.autosuggestProps?.resetValue(), 0);
- return (this.props.contents instanceof ObjectField ? (null) :
- <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
- ref={this._ref}
- style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
- onClick={this.onClick} placeholder={this.props.placeholder}>
- {this.props.highlight === undefined || this.props.positions === undefined || this.props.bing === undefined ? <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
- : this.returnHighlights()}
- </div>
- );
+ </div> :
+ this.renderEditor();
}
+ setTimeout(() => this.props.autosuggestProps?.resetValue(), 0);
+ return this.props.contents instanceof ObjectField ? (null) :
+ <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} ref={this._ref}
+ style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
+ onClick={this.onClick} placeholder={this.props.placeholder}>
+ <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{
+ this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
+ </span>
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 76e786257..63711a3cb 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -7,7 +7,7 @@ import { Cast, FieldValue, NumCast } from "../../fields/Types";
import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter, setupMoveUpEvents } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter, setupMoveUpEvents, returnEmptyDoclist } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
@@ -26,7 +26,7 @@ import * as fitCurve from 'fit-curve';
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
@observer
-export default class GestureOverlay extends Touchable {
+export class GestureOverlay extends Touchable {
static Instance: GestureOverlay;
@observable public InkShape: string = "";
@@ -613,10 +613,10 @@ export default class GestureOverlay extends Touchable {
}
}
// if we're not drawing in a toolglass try to recognize as gesture
- else {
+ else { // need to decide when to turn gestures back on
const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
let actionPerformed = false;
- if (result && result.Score > 0.7) {
+ if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
switch (result.Name) {
case GestureUtils.Gestures.Box: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); break;
case GestureUtils.Gestures.StartBracket: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); break;
@@ -754,20 +754,64 @@ export default class GestureOverlay extends Touchable {
break;
case "circle":
+ // const centerX = (right + left) / 2;
+ // const centerY = (bottom + top) / 2;
+ // const radius = bottom - centerY;
+
+
+ // for (var y = top; y < bottom; y++) {
+ // const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ // this._points.push({ X: x, Y: y });
+ // }
+ // for (var y = bottom; y > top; y--) {
+ // const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ // const newX = centerX - (x - centerX);
+ // this._points.push({ X: newX, Y: y });
+ // }
+ // this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
+ // this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 });
+
const centerX = (right + left) / 2;
const centerY = (bottom + top) / 2;
- const radius = bottom - centerY;
- for (var y = top; y < bottom; y++) {
- const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
- this._points.push({ X: x, Y: y });
- }
- for (var y = bottom; y > top; y--) {
- const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
- const newX = centerX - (x - centerX);
- this._points.push({ X: newX, Y: y });
+ if ((bottom - centerY) < (right - centerX)) {
+ const radius = bottom - centerY;
+ for (var y = top; y < bottom; y++) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ this._points.push({ X: x, Y: y });
+ }
+ for (var y = bottom; y > top; y--) {
+ const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
+ const newX = centerX - (x - centerX);
+ this._points.push({ X: newX, Y: y });
+ }
+ this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
+ this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 });
+
+ } else {
+ //right = bottom
+ //left = top
+ const radius = right - centerX;
+ for (var x = left; x < right; x++) {
+ const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
+ this._points.push({ X: x, Y: y });
+ }
+ for (var x = right; x > left; x--) {
+ const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
+ const newY = centerY - (y - centerY);
+ this._points.push({ X: x, Y: newY });
+ }
+ this._points.push({ X: left, Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((left - centerX), 2))) + centerY });
+ this._points.push({ X: left, Y: (Math.sqrt(Math.pow(radius, 2) - (Math.pow((left - centerX), 2))) + centerY) - 1 });
+
+
}
- this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
- this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 });
+
+
+
+
+
+
+
break;
case "line":
@@ -891,6 +935,7 @@ export default class GestureOverlay extends Touchable {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
/>;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index be6aa6be2..83c02b09b 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -6,23 +6,26 @@ import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, PromiseValue } from "../../fields/Types";
-import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
import { DocServer } from "../DocServer";
import { DocumentType } from "../documents/DocumentTypes";
import { DictationManager } from "../util/DictationManager";
import { DragManager } from "../util/DragManager";
+import { GroupManager } from "../util/GroupManager";
import { SelectionManager } from "../util/SelectionManager";
-import SharingManager from "../util/SharingManager";
+import { SharingManager } from "../util/SharingManager";
import { undoBatch, UndoManager } from "../util/UndoManager";
import { CollectionDockingView } from "./collections/CollectionDockingView";
+import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
+import { ContextMenu } from "./ContextMenu";
import { DocumentDecorations } from "./DocumentDecorations";
+import { InkStrokeProperties } from "./InkStrokeProperties";
import { MainView } from "./MainView";
-import { DocumentView } from "./nodes/DocumentView";
import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
-import PDFMenu from "./pdf/PDFMenu";
-import { ContextMenu } from "./ContextMenu";
-import GroupManager from "../util/GroupManager";
-import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
+import { DocumentView } from "./nodes/DocumentView";
+import { PDFMenu } from "./pdf/PDFMenu";
+import { SnappingManager } from "../util/SnappingManager";
+import { SearchBox } from "./search/SearchBox";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -31,7 +34,7 @@ type KeyControlInfo = {
stopPropagation: boolean
};
-export default class KeyManager {
+export class KeyManager {
public static Instance: KeyManager = new KeyManager();
private router = new Map<string, KeyHandler>();
@@ -81,26 +84,18 @@ export default class KeyManager {
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
case "escape":
- // if (DocumentLinksButton.StartLink) {
- // if (DocumentLinksButton.StartLink.Document) {
- // action((e: React.PointerEvent<HTMLDivElement>) => {
- // Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
- // });
- // }
- // }
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = false);
- const main = MainView.Instance;
Doc.SetSelectedTool(InkTool.None);
var doDeselect = true;
- if (main.isPointerDown) {
+ if (SnappingManager.GetIsDragging()) {
DragManager.AbortDrag();
+ } else if (CollectionDockingView.Instance.HasFullScreen) {
+ CollectionDockingView.Instance.CloseFullScreen();
} else {
- if (CollectionDockingView.Instance.HasFullScreen()) {
- CollectionDockingView.Instance.CloseFullScreen();
- } else {
- doDeselect = !ContextMenu.Instance.closeMenu();
- }
+ doDeselect = !ContextMenu.Instance.closeMenu();
}
doDeselect && SelectionManager.DeselectAll();
DictationManager.Controls.stop();
@@ -113,30 +108,18 @@ export default class KeyManager {
break;
case "delete":
case "backspace":
- if (document.activeElement) {
- if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") {
- return { stopPropagation: false, preventDefault: false };
- }
+ if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ return { stopPropagation: false, preventDefault: false };
}
const selected = SelectionManager.SelectedDocuments().slice();
- UndoManager.RunInBatch(() => {
- selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
- }, "delete");
+ UndoManager.RunInBatch(() => selected.map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
SelectionManager.DeselectAll();
break;
- case "arrowleft":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-1, 0)), "nudge left");
- break;
- case "arrowright":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(1, 0)), "nudge right");
- break;
- case "arrowup":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -1)), "nudge up");
- break;
- case "arrowdown":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 1)), "nudge down");
- break;
+ case "arrowleft": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-1, 0)), "nudge left"); break;
+ case "arrowright": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(1, 0)), "nudge right"); break;
+ case "arrowup": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -1)), "nudge up"); break;
+ case "arrowdown": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 1)), "nudge down"); break;
}
return {
@@ -150,22 +133,10 @@ export default class KeyManager {
const preventDefault = false;
switch (keyname) {
- // case "~":
- // DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
- // stopPropagation = true;
- // preventDefault = true;
- case "arrowleft":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-10, 0)), "nudge left");
- break;
- case "arrowright":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(10, 0)), "nudge right");
- break;
- case "arrowup":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -10)), "nudge up");
- break;
- case "arrowdown":
- UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 10)), "nudge down");
- break;
+ case "arrowleft": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-10, 0)), "nudge left"); break;
+ case "arrowright": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(10, 0)), "nudge right"); break;
+ case "arrowup": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -10)), "nudge up"); break;
+ case "arrowdown": UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 10)), "nudge down"); break;
}
return {
@@ -204,55 +175,32 @@ export default class KeyManager {
switch (keyname) {
case "arrowright":
- if (document.activeElement) {
- if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") {
- return { stopPropagation: false, preventDefault: false };
- }
+ if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ return { stopPropagation: false, preventDefault: false };
}
- MainView.Instance.mainFreeform && CollectionDockingView.AddRightSplit(MainView.Instance.mainFreeform);
+ MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, "right");
break;
case "arrowleft":
- if (document.activeElement) {
- if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") {
- return { stopPropagation: false, preventDefault: false };
- }
+ if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ return { stopPropagation: false, preventDefault: false };
}
- MainView.Instance.mainFreeform && CollectionDockingView.CloseRightSplit(MainView.Instance.mainFreeform);
+ MainView.Instance.mainFreeform && CollectionDockingView.CloseSplit(MainView.Instance.mainFreeform);
break;
case "backspace":
- if (document.activeElement) {
- if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") {
- return { stopPropagation: false, preventDefault: false };
- }
+ if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ return { stopPropagation: false, preventDefault: false };
}
break;
case "t":
PromiseValue(Cast(Doc.UserDoc()["tabs-button-tools"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
- if (MainView.Instance.flyoutWidth === 240) {
- MainView.Instance.flyoutWidth = 0;
- } else {
- MainView.Instance.flyoutWidth = 240;
- }
- break;
- case "l":
- PromiseValue(Cast(Doc.UserDoc()["tabs-button-library"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
- if (MainView.Instance.flyoutWidth === 250) {
- MainView.Instance.flyoutWidth = 0;
- } else {
- MainView.Instance.flyoutWidth = 250;
- }
break;
case "f":
- PromiseValue(Cast(Doc.UserDoc()["tabs-button-search"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
- if (MainView.Instance.flyoutWidth === 400) {
- MainView.Instance.flyoutWidth = 0;
- } else {
- MainView.Instance.flyoutWidth = 400;
- }
+ SearchBox.Instance._searchFullDB = "My Stuff";
+ SearchBox.Instance.enter(undefined);
break;
case "o":
const target = SelectionManager.SelectedDocuments()[0];
- target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target);
+ target && CollectionDockingView.OpenFullScreen(target.props.Document);
break;
case "r":
preventDefault = false;
@@ -323,8 +271,6 @@ export default class KeyManager {
undoBatch(() => {
targetDataDoc[fieldKey] = new List<Doc>([...docList, ...added]);
targetDataDoc[fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- const lastModified = "lastModified";
- targetDataDoc[lastModified] = new DateField(new Date(Date.now()));
})();
}
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
new file mode 100644
index 000000000..ad5c70fb1
--- /dev/null
+++ b/src/client/views/InkStrokeProperties.ts
@@ -0,0 +1,268 @@
+import { action, computed, observable } from "mobx";
+import { ColorState } from 'react-color';
+import { Doc, Field, Opt } from "../../fields/Doc";
+import { Document } from "../../fields/documentSchemas";
+import { InkField } from "../../fields/InkField";
+import { Cast, NumCast } from "../../fields/Types";
+import { DocumentType } from "../documents/DocumentTypes";
+import { SelectionManager } from "../util/SelectionManager";
+import { undoBatch } from "../util/UndoManager";
+
+export class InkStrokeProperties {
+ static Instance: InkStrokeProperties | undefined;
+
+ private _lastFill = "#D0021B";
+ private _lastLine = "#D0021B";
+ private _lastDash = "2";
+
+ @observable _lock = false;
+ @observable _controlBtn = false;
+ @observable _currPoint = -1;
+
+ getField(key: string) {
+ return this.selectedInk?.reduce((p, i) =>
+ (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
+ }
+
+ @computed get selectedInk() {
+ const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+ return inks.length ? inks : undefined;
+ }
+ @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
+ @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
+ @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
+ @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+ @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+ @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+ @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+ @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+ @computed get shapeHgt() { return this.getField("_height"); }
+ @computed get shapeWid() { return this.getField("_width"); }
+ @computed get shapeXps() { return this.getField("x"); }
+ @computed get shapeYps() { return this.getField("y"); }
+ @computed get shapeRot() { return this.getField("rotation"); }
+ set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
+ set solidFil(value) { this.unFilled = !value; }
+ set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
+ set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
+ set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
+ set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
+ set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+ set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ set dashdStk(value) {
+ value && (this._lastDash = value) && (this.unStrokd = false);
+ this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
+ }
+ set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
+ set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
+ set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
+ set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
+ set shapeWid(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = Number(value);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
+ });
+ }
+ set shapeHgt(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = Number(value);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
+ });
+ }
+
+ constructor() {
+ InkStrokeProperties.Instance = this;
+ }
+
+ @undoBatch
+ @action
+ addPoints = (x: number, y: number, pts: { X: number, Y: number }[], index: number, control: { X: number, Y: number }[]) => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ var counter = 0;
+ for (var k = 0; k < index; k++) {
+ control.forEach(pt => (pts[k].X === pt.X && pts[k].Y === pt.Y) && counter++);
+ }
+ //decide where to put the new coordinate
+ const spNum = Math.floor(counter / 2) * 4 + 2;
+
+ for (var i = 0; i < spNum; i++) {
+ ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ for (var j = 0; j < 4; j++) {
+ newPoints.push({ X: x, Y: y });
+
+ }
+ for (var i = spNum; i < ink.length; i++) {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ this._currPoint = -1;
+ Doc.GetProto(doc).data = new InkField(newPoints);
+ }
+ }
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
+ deletePoints = () => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1 && this._currPoint !== -1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink && ink.length > 4) {
+ const newPoints: { X: number, Y: number }[] = [];
+ const toRemove = Math.floor(((this._currPoint + 2) / 4));
+ for (var i = 0; i < ink.length; i++) {
+ if (Math.floor((i + 2) / 4) !== toRemove) {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ }
+ this._currPoint = -1;
+ Doc.GetProto(doc).data = new InkField(newPoints);
+ if (newPoints.length === 4) {
+ const newerPoints: { X: number, Y: number }[] = [];
+ newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
+ newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
+ newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
+ newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
+ Doc.GetProto(doc).data = new InkField(newerPoints);
+
+ }
+ }
+ }
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
+ rotate = (angle: number) => {
+ const _centerPoints: { X: number, Y: number }[] = [];
+ SelectionManager.SelectedDocuments().forEach(action(inkView => {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const xs = ink.map(p => p.X);
+ const ys = ink.map(p => p.Y);
+ const left = Math.min(...xs);
+ const top = Math.min(...ys);
+ const right = Math.max(...xs);
+ const bottom = Math.max(...ys);
+ _centerPoints.push({ X: left, Y: top });
+ }
+ }
+ }));
+
+ var index = 0;
+ SelectionManager.SelectedDocuments().forEach(action(inkView => {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ doc.rotation = Number(doc.rotation) + Number(angle);
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+
+ const newPoints: { X: number, Y: number }[] = [];
+ ink.forEach(i => {
+ const newX = Math.cos(angle) * (i.X - _centerPoints[index].X) - Math.sin(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].X;
+ const newY = Math.sin(angle) * (i.X - _centerPoints[index].X) + Math.cos(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
+ newPoints.push({ X: newX, Y: newY });
+ });
+ Doc.GetProto(doc).data = new InkField(newPoints);
+ const xs = newPoints.map(p => p.X);
+ const ys = newPoints.map(p => p.Y);
+ const left = Math.min(...xs);
+ const top = Math.min(...ys);
+ const right = Math.max(...xs);
+ const bottom = Math.max(...ys);
+
+ doc._height = (bottom - top);
+ doc._width = (right - left);
+ }
+ index++;
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
+ control = (xDiff: number, yDiff: number, controlNum: number) => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+
+ const newPoints: { X: number, Y: number }[] = [];
+ const order = controlNum % 4;
+ for (var i = 0; i < ink.length; i++) {
+ if (controlNum === i ||
+ (order === 0 && i === controlNum + 1) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
+ (order === 3 && i === controlNum - 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)
+ || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
+ ) {
+ newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) });
+ }
+ else {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ }
+ const oldx = doc.x;
+ const oldy = doc.y;
+ const xs = ink.map(p => p.X);
+ const ys = ink.map(p => p.Y);
+ const left = Math.min(...xs);
+ const top = Math.min(...ys);
+ Doc.GetProto(doc).data = new InkField(newPoints);
+ const xs2 = newPoints.map(p => p.X);
+ const ys2 = newPoints.map(p => p.Y);
+ const left2 = Math.min(...xs2);
+ const top2 = Math.min(...ys2);
+ const right2 = Math.max(...xs2);
+ const bottom2 = Math.max(...ys2);
+ doc._height = (bottom2 - top2);
+ doc._width = (right2 - left2);
+ //if points move out of bounds
+
+ doc.x = oldx - (left - left2);
+ doc.y = oldy - (top - top2);
+
+ }
+ }
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
+ switchStk = (color: ColorState) => {
+ const val = String(color.hex);
+ this.colorStk = val;
+ return true;
+ }
+
+ @undoBatch
+ @action
+ switchFil = (color: ColorState) => {
+ const val = String(color.hex);
+ this.colorFil = val;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 41311ed86..da98eca73 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,27 +1,22 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
+import { action } from "mobx";
import { observer } from "mobx-react";
+import { Doc } from "../../fields/Doc";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
import { makeInterface } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
import { TraceMobx } from "../../fields/util";
+import { setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { InteractionUtils } from "../util/InteractionUtils";
+import { Scripting } from "../util/Scripting";
+import { UndoManager } from "../util/UndoManager";
import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
import "./InkingStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
-import { Scripting } from "../util/Scripting";
-import { Doc } from "../../fields/Doc";
-import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
-import { action } from "mobx";
-import { setupMoveUpEvents } from "../../Utils";
-import { undoBatch, UndoManager } from "../util/UndoManager";
-
-
-library.add(faPaintBrush);
+import { InkStrokeProperties } from "./InkStrokeProperties";
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@@ -47,11 +42,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this.props.Document.isInkMask = true;
}
- @action
- private formatShape = () => {
- FormatShapePane.Instance.Pinned = true;
- }
-
public _prevX = 0;
public _prevY = 0;
private _controlNum = 0;
@@ -66,15 +56,17 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
@action
changeCurrPoint = (i: number) => {
- FormatShapePane.Instance._currPoint = i;
+ if (!InkStrokeProperties.Instance) return;
+ InkStrokeProperties.Instance._currPoint = i;
document.addEventListener("keydown", this.delPts, true);
}
@action
onControlMove = (e: PointerEvent, down: number[]): boolean => {
+ if (!InkStrokeProperties.Instance) return false;
const xDiff = this._prevX - e.clientX;
const yDiff = this._prevY - e.clientY;
- FormatShapePane.Instance.control(xDiff, yDiff, this._controlNum);
+ InkStrokeProperties.Instance.control(xDiff, yDiff, this._controlNum);
this._prevX = e.clientX;
this._prevY = e.clientY;
return false;
@@ -89,8 +81,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
}
@action
delPts = (e: KeyboardEvent | React.PointerEvent | undefined) => {
- if (e instanceof KeyboardEvent ? e.key === "-" : true) {
- FormatShapePane.Instance.deletePoints();
+ if (InkStrokeProperties.Instance && (e instanceof KeyboardEvent ? e.key === "-" : true)) {
+ InkStrokeProperties.Instance.deletePoints();
}
}
@@ -98,34 +90,40 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
public static MaskDim = 50000;
render() {
TraceMobx();
+ const formatInstance = InkStrokeProperties.Instance;
+ if (!formatInstance) return (null);
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
// const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
const strokeWidth = Number(this.layoutDoc.strokeWidth);
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
- const left = Math.min(...xs) - strokeWidth / 2;
- const top = Math.min(...ys) - strokeWidth / 2;
- const right = Math.max(...xs) + strokeWidth / 2;
- const bottom = Math.max(...ys) + strokeWidth / 2;
- const width = Math.max(right - left);
+ const lineTop = Math.min(...ys);
+ const lineBot = Math.max(...ys);
+ const lineLft = Math.min(...xs);
+ const lineRgt = Math.max(...xs);
+ const left = lineLft - strokeWidth / 2;
+ const top = lineTop - strokeWidth / 2;
+ const right = lineRgt + strokeWidth / 2;
+ const bottom = lineBot + strokeWidth / 2;
+ const width = Math.max(1, right - left);
const height = Math.max(1, bottom - top);
const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
const strokeColor = StrCast(this.layoutDoc.color, "");
const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
+ StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
- StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
+ StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
const hpoints = InteractionUtils.CreatePolyline(data, left, top,
this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
- "none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true);
+ StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+ "none", "none", undefined, scaleX, scaleY, "", "visiblepainted", false, true);
//points for adding
const apoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
+ StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
@@ -161,36 +159,31 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const dotsize = String(Math.max(width * scaleX, height * scaleY) / 40);
const addpoints = apoints.map((pts, i) =>
-
- <svg height="10" width="10">
- <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="invisible" stroke-width={String(Number(dotsize) / 2)} fill="invisible"
- onPointerDown={(e) => { FormatShapePane.Instance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
+ <svg height="10" width="10" key={`add${i}`}>
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="invisible" strokeWidth={String(Number(dotsize) / 2)} fill="invisible"
+ onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
/>
</svg>);
const controls = controlPoints.map((pts, i) =>
-
- <svg height="10" width="10">
- <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="red"
+ <svg height="10" width="10" key={`ctrl${i}`}>
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" strokeWidth={String(Number(dotsize) / 2)} fill="red"
onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="all-scroll"
/>
</svg>);
const handles = handlePoints.map((pts, i) =>
-
- <svg height="10" width="10">
- <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="green"
- onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="all-scroll" display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+ <svg height="10" width="10" key={`hdl${i}`}>
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" strokeWidth={String(Number(dotsize) / 2)} fill="green"
+ onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="all-scroll" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
</svg>);
const handleLines = handleLine.map((pts, i) =>
-
- <svg height="100" width="100">
+ <svg height="100" width="100" key={`line${i}`}>
<line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
- x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" stroke-width={String(Number(dotsize) / 2)}
- display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+ x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={String(Number(dotsize) / 2)}
+ display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
<line x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
- x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" stroke-width={String(Number(dotsize) / 2)}
- display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
-
+ x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={String(Number(dotsize) / 2)}
+ display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
</svg>);
@@ -209,6 +202,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
if (cm) {
!Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+ cm.addItem({ description: "Edit Points", event: action(() => formatInstance._controlBtn = !formatInstance._controlBtn), icon: "paint-brush" });
//cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
}
}}
@@ -216,10 +210,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
</defs>
{hpoints}
{points}
- {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? addpoints : ""}
- {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? controls : ""}
- {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handles : ""}
- {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handleLines : ""}
+ {formatInstance._controlBtn && this.props.isSelected() ? addpoints : ""}
+ {formatInstance._controlBtn && this.props.isSelected() ? controls : ""}
+ {formatInstance._controlBtn && this.props.isSelected() ? handles : ""}
+ {formatInstance._controlBtn && this.props.isSelected() ? handleLines : ""}
</svg>
);
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index a05a2b858..9ca8f348d 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,6 +1,7 @@
@import "globalCssVariables";
@import "nodeModuleOverrides";
+
.dash-tooltip {
font-size: 11px;
padding: 2px;
@@ -14,18 +15,23 @@
margin-top: 10px;
}
+.mainContent-div-flyout,
.mainContent-div {
position: relative;
width: 100%;
height: 100%;
}
+.mainContent-div-flyout {
+ left: calc(-1 * var(--flyoutHandleWidth));
+}
// add nodes menu. Note that the + button is actually an input label, not an actual button.
.mainView-docButtons {
position: absolute;
- bottom: 35px;
+ bottom: 10px;
left: calc(100% + 5px);
z-index: 1;
+ pointer-events: none;
}
.mainView-snapLines {
@@ -110,13 +116,16 @@
height: 55px;
width: 17px;
position: absolute;
- top: 55%;
+ top: 50%;
border: 1px black solid;
border-radius: 0;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
border-right: unset;
- z-index: 2;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ padding: 4px;
.mainView-propertiesDragger-icon {
width: 10px;
@@ -143,26 +152,38 @@
cursor: auto;
}
-.mainView-flyoutContainer {
- display: flex;
- flex-direction: column;
+.mainView-innerContent {
+ display: contents;
+ flex-direction: row;
position: relative;
- height: 100%;
- background: dimgray;
+ .mainView-flyoutContainer {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ height: 100%;
+ background: dimgray;
- .documentView-node-topmost {
- background: lightgrey;
+ .documentView-node-topmost {
+ background: lightgrey;
+ }
+ }
+ .propertiesView {
+ right: 0;
+ position: absolute;
+ z-index: 2;
}
}
.mainView-menuPanel {
-
- width: 60px;
+ min-width: var(--menuPanelWidth);
background-color: #121721;
- height: calc(100% - $searchpanel-height);
- //overflow-y: scroll;
- //overflow-x: hidden;
+ .collectionStackingView {
+ scrollbar-width: none;
+ }
+ ::-webkit-scrollbar {
+ width:0;
+ }
.mainView-menuPanel-button {
padding: 7px;
@@ -265,14 +286,22 @@
transform: none !important;
}
+.mainView-libraryFlyout-out,
.mainView-libraryFlyout {
height: 100%;
width: 100%;
- position: absolute;
+ position: relative;
display: flex;
flex-direction: column;
z-index: 2;
+ .mainView-contentArea {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ overflow: visible;
+ }
+
.mainView-libraryFlyout-close {
right: 6;
top: 5;
@@ -282,26 +311,27 @@
margin-bottom: 10;
}
}
-
-.mainView-expandFlyoutButton {
- position: absolute;
- top: 120px;
- right: 55px;
- cursor: pointer;
+.mainView-libraryFlyout-out {
+ transition: width .25s;
+ box-shadow: rgb(156, 147, 150) 0.2vw 0.2vw 0.2vw;
}
+
.mainView-libraryHandle {
- width: 28px;
- left: calc(100% - 10px);
+ width: var(--flyoutHandleWidth);
height: 55px;
top: 50%;
+ left: -10px;
border: 1px solid black;
border-radius: 8px;
- position: absolute;
- z-index: 2;
+ position: relative;
+ z-index: 1;
touch-action: none;
background-color: lightgrey;
cursor: grab;
+ display: flex;
+ align-items: center;
+ padding: 12px;
.mainView-libraryHandle-icon {
width: 10px;
@@ -313,7 +343,7 @@
}
-.mainView-workspace {
+.mainView-dashboard {
height: 200px;
position: relative;
display: flex;
@@ -325,8 +355,25 @@
display: flex;
}
-.mainView-recentlyClosed {
+.mainView-inactiveDocs {
height: 25%;
position: relative;
display: flex;
+}
+
+.mainVew-webRef {
+ position: absolute;
+ left: -1000;
+ top: -1000;
+ display: block;
+ width: 200px;
+ height: 800px;
+}
+.mainVew-invisibleWebRef {
+ position: absolute;
+ left: 50;
+ top: 50;
+ display: block;
+ width: 500px;
+ height: 1000px;
} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 059e1f566..f9b3b1da8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,48 +1,44 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faHireAHelper, faBuffer } from '@fortawesome/free-brands-svg-icons';
+import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
+import { action, computed, configure, observable, reaction } 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 '../../fields/Doc';
-import { Id } from '../../fields/FieldSymbols';
+import * as ReactDOM from 'react-dom';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
-import { TraceMobx } from '../../fields/util';
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils, simulateMouseClick } from '../../Utils';
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { PrefetchProxy } from '../../fields/Proxy';
+import { BoolCast, PromiseValue, StrCast } from '../../fields/Types';
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
-import { Docs, DocumentOptions } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DocumentManager } from '../util/DocumentManager';
-import GroupManager from '../util/GroupManager';
+import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
+import { Hypothesis } from '../util/HypothesisUtils';
import { Scripting } from '../util/Scripting';
-import { SelectionManager } from '../util/SelectionManager';
-import SettingsManager from '../util/SettingsManager';
-import SharingManager from '../util/SharingManager';
+import { SettingsManager } from '../util/SettingsManager';
+import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
-import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
-import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import { PropertiesView } from './collections/collectionFreeForm/PropertiesView';
+import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/CollectionLinearView';
-import CollectionMenu from './collections/CollectionMenu';
-import { CollectionView, CollectionViewType } from './collections/CollectionView';
+import { CollectionMenu } from './collections/CollectionMenu';
+import { CollectionViewType } from './collections/CollectionView';
import { ContextMenu } from './ContextMenu';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
-import GestureOverlay from './GestureOverlay';
-import { ANTIMODEMENU_HEIGHT, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
-import KeyManager from './GlobalKeyHandler';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { GestureOverlay } from './GestureOverlay';
+import { MENU_PANEL_WIDTH, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
+import { KeyManager } from './GlobalKeyHandler';
import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
import { AudioBox } from './nodes/AudioBox';
@@ -53,70 +49,46 @@ import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview } from './nodes/LinkDocPreview';
import { RadialMenu } from './nodes/RadialMenu';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
+import { WebBox } from './nodes/WebBox';
import { OverlayView } from './OverlayView';
-import PDFMenu from './pdf/PDFMenu';
+import { PDFMenu } from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
-import { Hypothesis } from '../util/HypothesisUtils';
-import { WebBox } from './nodes/WebBox';
-import * as ReactDOM from 'react-dom';
+import { PropertiesView } from './PropertiesView';
import { SearchBox } from './search/SearchBox';
-import { SearchUtil } from '../util/SearchUtil';
-import { Networking } from '../Network';
-import * as rp from 'request-promise';
-import { LinkManager } from '../util/LinkManager';
-import RichTextMenu from './nodes/formattedText/RichTextMenu';
+import { TraceMobx } from '../../fields/util';
+import { SelectionManager } from '../util/SelectionManager';
+const _global = (window /* browser */ || global /* node */) as any;
@observer
export class MainView extends React.Component {
public static Instance: MainView;
- private _buttonBarHeight = 36;
- private _flyoutSizeOnDown = 0;
- private _urlState: HistoryUtil.DocUrl;
private _docBtnRef = React.createRef<HTMLDivElement>();
private _mainViewRef = React.createRef<HTMLDivElement>();
+ private _lastButton: Doc | undefined;
@observable private _panelWidth: number = 0;
@observable private _panelHeight: number = 0;
- @observable private _flyoutTranslate: boolean = false;
- @observable public flyoutWidth: number = 0;
- private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
+ @observable private _panelContent: string = "none";
+ @observable private _sidebarContent: any = this.userDoc?.sidebar;
+ @observable private _flyoutWidth: number = 0;
+ @computed private get topOffset() { return (CollectionMenu.Instance?.Pinned ? 35 : 0) + Number(SEARCH_PANEL_HEIGHT.replace("px", "")); }
@computed private get userDoc() { return Doc.UserDoc(); }
- @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
+ @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+ @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
@computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
- @computed public get searchDoc() { return Cast(this.userDoc["search-panel"], Doc) as Doc; }
-
- @observable public sidebarContent: any = this.userDoc?.sidebar;
- @observable public panelContent: string = "none";
- @observable public showProperties: boolean = false;
- public isPointerDown = false;
- @computed get selectedDocumentView() {
- if (SelectionManager.SelectedDocuments().length) {
- return SelectionManager.SelectedDocuments()[0];
- } else { return undefined; }
- }
-
- propertiesWidth = () => Math.max(0, Math.min(this._panelWidth - 50, CurrentUserUtils.propertiesWidth));
- @computed get propertiesIcon() {
- if (this.propertiesWidth() < 10) {
- return "chevron-left";
- } else {
- return "chevron-right";
- }
- }
- @observable propertiesDownX: number | undefined;
+ menuPanelWidth = () => Number(MENU_PANEL_WIDTH.replace("px", ""));
+ propertiesWidth = () => Math.max(0, Math.min(this._panelWidth - 50, CurrentUserUtils.propertiesWidth || 0));
componentDidMount() {
+ new InkStrokeProperties();
+ this._sidebarContent.proto = undefined;
DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
- const tag = document.createElement('script');
-
- const proto = DocServer.GetRefField("rtfProto").then(proto => {
- (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE),
- msg => msg && alert(msg));
- });
+ DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
+ const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
@@ -125,11 +97,7 @@ export class MainView extends React.Component {
window.addEventListener("paste", KeyManager.Instance.paste as any);
document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on
const id = FormattedTextBox.GetDocFromUrl(e.detail);
- DocServer.GetRefField(id).then(doc => {
- if (doc instanceof Doc) {
- DocumentManager.Instance.jumpToDocument(doc, false, undefined);
- }
- });
+ DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined) : (null));
});
document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
}
@@ -137,7 +105,6 @@ export class MainView extends React.Component {
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);
document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener);
}
@@ -145,212 +112,125 @@ export class MainView extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
MainView.Instance = this;
- this._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
- // causes errors to be generated when modifying an observable outside of an action
-
- CurrentUserUtils.propertiesWidth = 0;
+ CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
+ // causes errors to be generated when modifying an observable outside of an action
configure({ enforceActions: "observed" });
+
if (window.location.pathname !== "/home") {
const pathname = window.location.pathname.substr(1).split("/");
- if (pathname.length > 1) {
- const type = pathname[0];
- if (type === "doc") {
- CurrentUserUtils.MainDocId = pathname[1];
- if (!this.userDoc) {
- runInAction(() => this.closeFlyout());
- DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt<Field>) =>
- field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
- }
- }
+ if (pathname.length > 1 && pathname[0] === "doc") {
+ CurrentUserUtils.MainDocId = pathname[1];
+ !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
}
}
library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar,
- fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
- fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
- fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
+ fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
+ fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
+ fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
- fa.faSearch, fa.faFileDownload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
- fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faClipboard,
+ fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
+ fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp,
fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
- fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
+ fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote,
fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
- fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
+ fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper,
fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle,
fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
- fa.faWindowMinimize, fa.faWindowRestore);
+ fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
+ fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
+ fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload,
+ fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines);
this.initEventListeners();
this.initAuthenticationRouters();
}
globalPointerDown = action((e: PointerEvent) => {
- this.isPointerDown = true;
AudioBox.Enabled = true;
const targets = document.elementsFromPoint(e.x, e.y);
- if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
- ContextMenu.Instance.closeMenu();
- }
- if (targets && (targets.length && targets[0].className.toString() !== "timeline-menu-desc" && targets[0].className.toString() !== "timeline-menu-item" && targets[0].className.toString() !== "timeline-menu-input")) {
- TimelineMenu.Instance.closeMenu();
- }
- if (targets && targets.length && SearchBox.Instance._searchbarOpen) {
- let check = false;
- const icon = "icon";
- targets.forEach((thing) => {
- if (thing.className.toString() === "collectionSchemaView-searchContainer" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "collectionSchema-header-menuOptions" || thing.className.toString() === "altcollectionTimeView-treeView") {
- check = true;
- }
- });
- if (check === false) {
- SearchBox.Instance.closeSearch();
+ if (targets.length) {
+ const targClass = targets[0].className.toString();
+ if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
+ const check = targets.some((thing) =>
+ (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
+ thing.className === "collectionSchema-header-menuOptions"));
+ !check && SearchBox.Instance.resetSearch(true);
}
+ !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
+ !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
}
-
-
});
- globalPointerUp = () => this.isPointerDown = false;
-
initEventListeners = () => {
- window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
- window.addEventListener("dragover", (e) => { e.preventDefault(); }, false); // drag event handler
- // click interactions for the context menu
+ window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
+ window.addEventListener("dragover", e => e.preventDefault(), false);
document.addEventListener("pointerdown", this.globalPointerDown);
- document.addEventListener("pointerup", this.globalPointerUp);
+ document.addEventListener("click", (e: MouseEvent) => {
+ if (!e.cancelBubble) {
+ const pathstr = (e as any)?.path.map((p: any) => p.classList?.toString()).join();
+ if (pathstr.includes("libraryFlyout")) {
+ SelectionManager.DeselectAll();
+ }
+ }
+ });
}
initAuthenticationRouters = async () => {
- // Load the user's active workspace, or create a new one if initial session after signup
+ // Load the user's active dashboard, or create a new one if initial session after signup
const received = CurrentUserUtils.MainDocId;
if (received && !this.userDoc) {
- reaction(
- () => CurrentUserUtils.GuestTarget,
- target => target && this.createNewWorkspace(),
- { fireImmediately: true }
- );
+ reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true });
} else {
- if (received && this._urlState.sharing) {
+ if (received && CurrentUserUtils._urlState.sharing) {
reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
- CollectionDockingView.AddRightSplit(docField);
+ CollectionDockingView.AddSplit(docField, "right");
}
}),
);
}
- const doc = this.userDoc && await Cast(this.userDoc.activeWorkspace, Doc);
- if (doc) {
- this.openWorkspace(doc);
- } else {
- this.createNewWorkspace();
- }
- }
- }
-
- @action
- createNewWorkspace = async (id?: string) => {
- const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
- const workspaceCount = DocListCast(workspaces.data).length + 1;
- const freeformOptions: DocumentOptions = {
- x: 0,
- y: 400,
- _width: this._panelWidth * .7 - this.propertiesWidth() * 0.7,
- _height: this._panelHeight,
- title: "Untitled Collection",
- };
- const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const workspaceDoc = 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`);
- const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
- const copyWorkspace = ScriptField.MakeScript(`copyWorkspace()`);
- workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, copyWorkspace!]);
- workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Workspace"]);
-
- Doc.AddDocToList(workspaces, "data", workspaceDoc);
- // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
- setTimeout(() => this.openWorkspace(workspaceDoc), 0);
- }
-
- @action
- openWorkspace = (doc: Doc, fromHistory = false) => {
- CurrentUserUtils.MainDocId = doc[Id];
-
- if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest workspace
- !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]));
- this.userDoc ? (this.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc);
- }
- const state = this._urlState;
- if (state.sharing === true && !this.userDoc) {
- DocServer.Control.makeReadOnly();
- } else {
- fromHistory || HistoryUtil.pushState({
- type: "doc",
- docId: doc[Id],
- readonly: state.readonly,
- nro: state.nro,
- sharing: false,
+ const activeDash = PromiseValue(this.userDoc.activeDashboard);
+ activeDash.then(dash => {
+ if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash);
+ else CurrentUserUtils.createNewDashboard(this.userDoc);
});
- if (state.readonly === true || state.readonly === null) {
- DocServer.Control.makeReadOnly();
- } else if (state.safe) {
- if (!state.nro) {
- DocServer.Control.makeReadOnly();
- }
- CollectionView.SetSafeMode(true);
- } else if (state.nro || state.nro === null || state.readonly === false) {
- } else if (doc.readOnly) {
- DocServer.Control.makeReadOnly();
- } else {
- DocServer.Control.makeEditable();
- }
}
-
- return true;
- }
-
- onDrop = (e: React.DragEvent<HTMLDivElement>) => {
- e.preventDefault();
- e.stopPropagation();
}
@action
- onResize = (r: any) => {
- this._panelWidth = r.offset.width;// - this.propertiesWidth();
- this._panelHeight = r.offset.height;
+ createNewPresentation = async () => {
+ if (!await this.userDoc.myPresentations) {
+ this.userDoc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "PRESENTATION TRAILS", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, system: true
+ }));
+ }
+ const pres = Docs.Create.PresDocument(new List<Doc>(),
+ { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true });
+ CollectionDockingView.AddSplit(pres, "right");
+ this.userDoc.activePresentation = pres;
+ Doc.AddDocToList(this.userDoc.myPresentations as Doc, "data", pres);
}
- @action
- getPWidth = () => this._panelWidth - this.propertiesWidth()
-
+ getPWidth = () => this._panelWidth - this.propertiesWidth();
getPHeight = () => this._panelHeight;
- getContentsHeight = () => this._panelHeight - this._buttonBarHeight;
+ getContentsHeight = () => this._panelHeight - Number(SEARCH_PANEL_HEIGHT.replace("px", ""));
defaultBackgroundColors = (doc: Opt<Doc>, renderDepth: number) => {
- if (this.panelContent === doc?.title) return "lightgrey";
-
if (doc?.type === DocumentType.COL) {
- if (doc.title === "Basic Item Creators" || doc.title === "sidebar-tools"
- || doc.title === "sidebar-recentlyClosed" || doc.title === "sidebar-catalog"
- || doc.title === "Mobile Uploads" || doc.title === "COLLECTION_PROTO"
- || doc.title === "Advanced Item Prototypes" || doc.title === "all Creators") {
- return "lightgrey";
- }
- return StrCast(renderDepth > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground);
+ return Doc.IsSystem(doc) ? "lightgrey" : StrCast(renderDepth > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground);
}
if (this.darkScheme) {
switch (doc?.type) {
case DocumentType.FONTICON: return "white";
case DocumentType.RTF || DocumentType.LABEL || DocumentType.BUTTON: return "#2d2d2d";
case DocumentType.LINK:
- case DocumentType.COL: {
- if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
- }
+ case DocumentType.COL: if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
default: return "black";
}
} else {
@@ -360,9 +240,7 @@ export class MainView extends React.Component {
case DocumentType.BUTTON:
case DocumentType.LABEL: return "lightgray";
case DocumentType.LINK:
- case DocumentType.COL: {
- if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray";
- }
+ case DocumentType.COL: if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray";
default: return "white";
}
}
@@ -391,6 +269,7 @@ export class MainView extends React.Component {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
renderDepth={-1}
@@ -398,60 +277,41 @@ export class MainView extends React.Component {
}
@computed get dockingContent() {
- TraceMobx();
- const mainContainer = this.mainContainer;
- const width = this.flyoutWidth + this.propertiesWidth();
- return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - ${SEARCH_PANEL_HEIGHT})` }}>
- {!mainContainer ? (null) : this.mainDocView}
+ return <div className={`mainContent-div${this._flyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
+ style={{ minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`, width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)` }}>
+ {!this.mainContainer ? (null) : this.mainDocView}
</div>;
}
@action
onPropertiesPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
- CurrentUserUtils.propertiesWidth = this._panelWidth - e.clientX;
- return false;
- }), returnFalse, action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._panelWidth - 50, 250) : 0), false);
+ setupMoveUpEvents(this, e,
+ action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._panelWidth - e.clientX)) ? false : false),
+ action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)),
+ action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._panelWidth - 50, 250) : 0), false);
}
@action
onFlyoutPointerDown = (e: React.PointerEvent) => {
- if (this._flyoutTranslate) {
- setupMoveUpEvents(this, e, action((e: PointerEvent) => {
- this.flyoutWidth = Math.max(e.clientX, 0);
- if (this.flyoutWidth < 5) {
- this.panelContent = "none";
- this._lastButton && (this._lastButton.color = "white");
- this._lastButton && (this._lastButton._backgroundColor = "");
- }
- return false;
- }), emptyFunction, action(() => {
- if (this.flyoutWidth < 15) MainView.expandFlyout();
- else this.closeFlyout();
- }));
- }
+ setupMoveUpEvents(this, e,
+ action(e => (this._flyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false),
+ () => this._flyoutWidth < 5 && this.closeFlyout(),
+ this.closeFlyout);
}
- flyoutWidthFunc = () => this.flyoutWidth;
- addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
- return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
- doc.dockingConfig ? this.openWorkspace(doc) :
- CollectionDockingView.AddRightSplit(doc, libraryPath);
- }
+ flyoutWidthFunc = () => this._flyoutWidth;
sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0) - Number(SEARCH_PANEL_HEIGHT.replace("px", "")), 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-58, 0);
+ addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
+ return where === "close" ? CollectionDockingView.CloseSplit(doc) :
+ doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) : CollectionDockingView.AddSplit(doc, "right");
+ }
@computed get flyout() {
- if (!this.sidebarContent) return null;
- return <div className="mainView-libraryFlyout">
- <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${SEARCH_PANEL_HEIGHT})`, width: "100%", overflow: "visible" }}>
- {/* {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close"
- onPointerDown={this.closeFlyout}>
- <FontAwesomeIcon icon="times" color="black" size="lg" />
- </div> : null} */}
-
+ return <div className={`mainView-libraryFlyout${this._flyoutWidth ? "" : "-out"}`} style={{ minWidth: this._flyoutWidth, width: this._flyoutWidth }} >
+ <div className="mainView-contentArea" >
<DocumentView
- Document={this.sidebarContent}
+ Document={this._sidebarContent}
DataDoc={undefined}
LibraryPath={emptyPath}
addDocument={undefined}
@@ -473,13 +333,15 @@ export class MainView extends React.Component {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
relative={true}
forcedBackgroundColor={() => "lightgrey"}
/>
</div>
- {this.docButtons}</div>;
+ {this.docButtons}
+ </div>;
}
@computed get menuPanel() {
@@ -498,7 +360,7 @@ export class MainView extends React.Component {
onClick={undefined}
ScreenToLocalTransform={this.sidebarScreenToLocal}
ContentScaling={returnOne}
- PanelWidth={() => 60}
+ PanelWidth={this.menuPanelWidth}
PanelHeight={this.getContentsHeight}
renderDepth={0}
focus={emptyFunction}
@@ -507,6 +369,7 @@ export class MainView extends React.Component {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
relative={true}
@@ -515,159 +378,94 @@ export class MainView extends React.Component {
</div>;
}
-
- @action
- closeFlyout = () => {
- this._lastButton && (this._lastButton.color = "white");
- this._lastButton && (this._lastButton._backgroundColor = "");
- this.panelContent = "none";
- this.flyoutWidth = 0;
- }
-
- get groupManager() { return GroupManager.Instance; }
-
- _lastButton: Doc | undefined;
@action
selectMenu = (button: Doc) => {
const title = StrCast(Doc.GetProto(button).title);
- this._lastButton && (this._lastButton.color = "white");
- this._lastButton && (this._lastButton._backgroundColor = "");
- if (this.panelContent === title && this.flyoutWidth !== 0) {
- this.panelContent = "none";
- this.flyoutWidth = 0;
- } else {
- let panelDoc: Doc | undefined;
- switch (this.panelContent = title) {
- case "Tools": panelDoc = Doc.UserDoc()["sidebar-tools"] as Doc ?? undefined; break;
- case "Workspace": panelDoc = Doc.UserDoc()["sidebar-workspaces"] as Doc ?? undefined; break;
- case "Catalog": panelDoc = Doc.UserDoc()["sidebar-catalog"] as Doc ?? undefined; break;
- case "Archive": panelDoc = Doc.UserDoc()["sidebar-recentlyClosed"] as Doc ?? undefined; break;
- case "Settings": SettingsManager.Instance.open(); break;
- case "Import": panelDoc = Doc.UserDoc()["sidebar-import"] as Doc ?? undefined; break;
- case "Sharing": panelDoc = Doc.UserDoc()["sidebar-sharing"] as Doc ?? undefined; break;
- case "User Doc": panelDoc = Doc.UserDoc()["sidebar-userDoc"] as Doc ?? undefined; break;
+ const willOpen = !this._flyoutWidth || this._panelContent !== title;
+ this.closeFlyout();
+ if (willOpen) {
+ switch (this._panelContent = title) {
+ case "Settings":
+ SettingsManager.Instance.open();
+ break;
+ case "Catalog":
+ SearchBox.Instance._searchFullDB = "My Stuff";
+ SearchBox.Instance.enter(undefined);
+ break;
+ default:
+ this.expandFlyout(button);
}
- this.sidebarContent.proto = panelDoc;
- if (panelDoc) {
- MainView.expandFlyout();
- button._backgroundColor = "lightgrey";
- button.color = "black";
- this._lastButton = button;
- } else this.flyoutWidth = 0;
}
return true;
}
- @action
- closeProperties = () => {
- CurrentUserUtils.propertiesWidth = 0;
- }
-
- @computed get propertiesView() {
- TraceMobx();
- return <div className="mainView-propertiesView" style={{
- overflow: this.propertiesWidth() < 15 ? "hidden" : undefined
- }}>
- <PropertiesView
- width={this.propertiesWidth()}
- height={this._panelHeight}
- renderDepth={1}
- ScreenToLocalTransform={Transform.Identity}
- onDown={this.closeProperties}
- />
- </div>;
- }
-
@computed get mainInnerContent() {
- const rightFlyout = this.propertiesWidth() - 1;
return <>
{this.menuPanel}
- <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
- <div className="mainView-flyoutContainer" style={{ width: this.flyoutWidth }}>
- {this.flyoutWidth !== 0 ? <div className="mainView-libraryHandle"
- onPointerDown={this.onFlyoutPointerDown}
- //style={{ backgroundColor: '#8c8b8b' }}
- >
- <span title="library View Dragger" style={{
- width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
- //height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
- position: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "absolute" : "fixed",
- top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "0"
- }} />
- <div className="mainview-libraryHandle-icon">
- <FontAwesomeIcon icon="chevron-left" color="black" size="sm" />
- </div>
- </div> : null}
- <div className="mainView-libraryFlyout" style={{
- //transformOrigin: this._flyoutTranslate ? "" : "left center",
- transition: this._flyoutTranslate ? "" : "width .5s",
- //transform: `scale(${this._flyoutTranslate ? 1 : 0.8})`,
- boxShadow: this._flyoutTranslate ? "" : "rgb(156, 147, 150) 0.2vw 0.2vw 0.2vw"
- }}>
- {this.flyout}
- {this.expandButton}
- </div>
+ <div className="mainView-innerContent" >
+ {this.flyout}
+ <div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
+ <FontAwesomeIcon icon="chevron-left" color="black" size="sm" />
</div>
+
{this.dockingContent}
- {this.showProperties ? (null) :
- <div className="mainView-propertiesDragger" title="Properties View Dragger" onPointerDown={this.onPropertiesPointerDown}
- style={{ right: rightFlyout, top: "50%" }}>
- <div className="mainView-propertiesDragger-icon">
- <FontAwesomeIcon icon={this.propertiesIcon} color="black" size="sm" /> </div>
- </div>
- }
- {this.propertiesWidth() < 10 ? (null) :
- <div style={{ width: this.propertiesWidth(), height: "calc(100% - 35px)" }}> {this.propertiesView} </div>}
+
+ <div className="mainView-propertiesDragger" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
+ <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color="black" size="sm" />
+ </div>
+ {this.propertiesWidth() < 10 ? (null) : <PropertiesView width={this.propertiesWidth()} height={this.getContentsHeight()} />}
</div>
</>;
}
@computed get mainContent() {
- const n = (CollectionMenu.Instance?.Pinned ? 1 : 0);
- const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
- const pinned = FormatShapePane.Instance?.Pinned;
- const innerContent = this.mainInnerContent;
- return !this.userDoc ? (null) : (
- <Measure offset onResize={this.onResize}>
- {({ measureRef }) =>
- <div className="mainView-mainContent" ref={measureRef} style={{
- color: this.darkScheme ? "rgb(205,205,205)" : "black",
- //change to times 2 for both pinned
- height,
- width: pinned ? `calc(100% - 200px)` : "100%"
- }} >
- {innerContent}
- </div>
- }
- </Measure>);
+ return !this.userDoc ? (null) :
+ <div className="mainView-mainContent" ref={r => {
+ r && new _global.ResizeObserver(action(() => { this._panelWidth = r.getBoundingClientRect().width; this._panelHeight = r.getBoundingClientRect().height; })).observe(r);
+ }} style={{
+ color: this.darkScheme ? "rgb(205,205,205)" : "black",
+ height: `calc(100% - ${this.topOffset}px)`,
+ width: "100%",
+ }} >
+ {this.mainInnerContent}
+ </div>;
}
- public static expandFlyout = action(() => {
- MainView.Instance._flyoutTranslate = true;
- MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250);
-
+ expandFlyout = action((button: Doc) => {
+ this._flyoutWidth = (this._flyoutWidth || 250);
+ this._sidebarContent.proto = button.target as any;
+ button._backgroundColor = "lightgrey";
+ button.color = "black";
+ this._lastButton = button;
});
- @computed get expandButton() {
- return !this._flyoutTranslate ? (<div className="mainView-expandFlyoutButton" title="Re-attach sidebar" onPointerDown={MainView.expandFlyout}></div>) : (null);
- }
+ closeFlyout = action(() => {
+ this._lastButton && (this._lastButton.color = "white");
+ this._lastButton && (this._lastButton._backgroundColor = "");
+ this._panelContent = "none";
+ this._sidebarContent.proto = undefined;
+ this._flyoutWidth = 0;
+ });
- 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);
+ addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => {
+ const ret = flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc);
+ ret && (doc._stayInCollection = undefined);
+ return ret;
+ }, true)
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
}
+
@computed get docButtons() {
- const dockedBtns = Doc.UserDoc()?.dockedBtns;
- if (dockedBtns instanceof Doc) {
- return <div className="mainView-docButtons" ref={this._docBtnRef}
- style={{ height: !dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
+ return !(this.userDoc.dockedBtns instanceof Doc) ? (null) :
+ <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !this.userDoc.dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
<CollectionLinearView
- Document={dockedBtns}
+ Document={this.userDoc.dockedBtns}
DataDoc={undefined}
LibraryPath={emptyPath}
fieldKey={"data"}
@@ -696,23 +494,13 @@ export class MainView extends React.Component {
focus={emptyFunction}
whenActiveChanged={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
- }
- return (null);
- }
-
- get mainViewElement() {
- return document.getElementById("mainView-container");
- }
-
- get mainViewRef() {
- return this._mainViewRef;
}
-
@computed get snapLines() {
- return !Doc.UserDoc().showSnapLines ? (null) : <div className="mainView-snapLines">
+ return !this.userDoc.showSnapLines ? (null) : <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"} />)}
@@ -742,12 +530,18 @@ export class MainView extends React.Component {
</defs>
</svg>;
}
+ select = (ctrlPressed: boolean) => { };
@computed get search() {
+ TraceMobx();
return <div className="mainView-searchPanel">
- {/* <div style={{ float: "left", marginLeft: "10px" }}>{Doc.CurrentUserEmail}</div> */}
- <div><DocumentView Document={this.searchDoc}
- DataDoc={undefined}
+ <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
+ DataDoc={CurrentUserUtils.MySearchPanelDoc}
+ fieldKey="data"
+ dropAction="move"
+ isSelected={returnTrue}
+ active={returnTrue}
+ select={this.select}
LibraryPath={emptyPath}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
@@ -764,19 +558,18 @@ export class MainView extends React.Component {
PanelHeight={this.getPHeight}
renderDepth={0}
focus={emptyFunction}
- parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- /></div>
+ ContainingCollectionDoc={undefined} />
</div>;
}
@computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
return !DocumentLinksButton.invisibleWebDoc ? null :
- <div style={{ position: 'absolute', left: 50, top: 50, display: 'block', width: '500px', height: '1000px' }} ref={DocumentLinksButton.invisibleWebRef}>
+ <div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
<WebBox
fieldKey={"data"}
ContainingCollectionView={undefined}
@@ -801,13 +594,13 @@ export class MainView extends React.Component {
NativeWidth={() => 800}
ContentScaling={returnOne}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
/>
</div>;
}
render() {
- return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
-
+ return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} onScroll={() => document.getElementById("root")!.scrollTop = 0} ref={this._mainViewRef}>
{this.inkResources}
<DictationOverlay />
<SharingManager />
@@ -817,8 +610,6 @@ export class MainView extends React.Component {
<DocumentDecorations />
{this.search}
<CollectionMenu />
- <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
- <FormatShapePane />
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
@@ -830,15 +621,13 @@ export class MainView extends React.Component {
<PreviewCursor />
<TaskCompletionBox />
<ContextMenu />
- <FormatShapePane />
<RadialMenu />
<PDFMenu />
<MarqueeOptionsMenu />
-
<OverlayView />
<TimelineMenu />
{this.snapLines}
- <div ref={this.makeWebRef} style={{ position: 'absolute', left: -1000, top: -1000, display: 'block', width: '200px', height: '800px' }} />
+ <div className="mainView-webRef" ref={this.makeWebRef} />
</div >);
}
@@ -847,7 +636,7 @@ export class MainView extends React.Component {
invisibleDoc => {
ReactDOM.unmountComponentAtNode(ele);
invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
- <div style={{ position: 'absolute', left: -1000, top: -1000, display: 'block', width: '200px', height: '800px' }} ref={DocumentLinksButton.invisibleWebRef}>
+ <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}>
<WebBox
fieldKey={"data"}
ContainingCollectionView={undefined}
@@ -872,112 +661,26 @@ export class MainView extends React.Component {
NativeWidth={() => 800}
ContentScaling={returnOne}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
/>
</div>;
</span>, ele);
- var success = false;
+ let success = false;
const onSuccess = () => {
success = true;
clearTimeout(interval);
document.removeEventListener("editSuccess", onSuccess);
};
- // For some reason, Hypothes.is annotations don't load until a click is registered on the page,
+ // For some reason, Hypothes.is annotations don't load until a click is registered on the page,
// so we keep simulating clicks until annotations have loaded and editing is successful
- const interval = setInterval(() => {
- !success && simulateMouseClick(ele, 50, 50, 50, 50);
- }, 500);
-
+ const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500);
setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
document.addEventListener("editSuccess", onSuccess);
});
}
-
- importDocument = () => {
- const sidebar = Cast(Doc.UserDoc()["sidebar-import-documents"], Doc, null);
- const sidebarDocView = DocumentManager.Instance.getDocumentView(sidebar);
- const input = document.createElement("input");
- input.type = "file";
- input.multiple = true;
- input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
- input.onchange = async _e => {
- const upload = Utils.prepend("/uploadDoc");
- const formData = new FormData();
- const file = input.files && input.files[0];
- if (file && file.type === 'application/zip') {
- formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json !== "error") {
- const doc = await DocServer.GetRefField(json);
- if (doc instanceof Doc && sidebarDocView) {
- sidebarDocView.props.addDocument?.(doc);
- setTimeout(() => {
- SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
- docs.docs.forEach(d => LinkManager.Instance.addLink(d));
- });
- }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
-
- }
- }
- } else if (input.files && input.files.length !== 0) {
- const files = input.files || [];
- Array.from(files).forEach(async file => {
- const res = await Networking.UploadFilesToServer(file);
- res.map(async ({ result }) => {
- const name = file.name;
- if (result instanceof Error) {
- return;
- }
- const path = Utils.prepend(result.accessPaths.agnostic.client);
- let doc: Doc;
- // Case 1: File is a video
- if (file.type.includes("video")) {
- doc = Docs.Create.VideoDocument(path, { _height: 100, title: name });
- // Case 2: File is a PDF document
- } else if (file.type === "application/pdf") {
- doc = Docs.Create.PdfDocument(path, { _height: 100, _fitWidth: true, title: name });
- // Case 3: File is an image
- } else if (file.type.includes("image")) {
- doc = Docs.Create.ImageDocument(path, { _height: 100, title: name });
- // Case 4: File is an audio document
- } else {
- doc = Docs.Create.AudioDocument(path, { title: name });
- }
- const res = await rp.get(Utils.prepend("/getUserDocumentId"));
- if (!res) {
- throw new Error("No user id returned");
- }
- const field = await DocServer.GetRefField(res);
- let pending: Opt<Doc>;
- if (field instanceof Doc) {
- pending = sidebar;
- }
- if (pending) {
- const data = await Cast(pending.data, listSpec(Doc));
- if (data) data.push(doc);
- else pending.data = new List([doc]);
- }
- });
- });
- } else {
- console.log("No file selected");
- }
- };
- input.click();
- }
}
+
Scripting.addGlobal(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); });
-Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
-Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; });
-Scripting.addGlobal(function copyWorkspace() {
- const copiedWorkspace = Doc.MakeCopy(Cast(Doc.UserDoc().activeWorkspace, Doc, null), true);
- const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
- Doc.AddDocToList(workspaces, "data", copiedWorkspace);
- // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
- setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0);
-});
-Scripting.addGlobal(function importDocument() { return MainView.Instance.importDocument(); },
- "imports files from device directly into the import sidebar");
+Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }); \ No newline at end of file
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 19387f619..34e0ff126 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -14,7 +14,7 @@ export interface MainViewOverlayProps {
}
@observer
-export default class MainViewModal extends React.Component<MainViewOverlayProps> {
+export class MainViewModal extends React.Component<MainViewOverlayProps> {
render() {
const p = this.props;
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 49580cde4..cc0dd0443 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,10 +1,11 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
+import ReactLoading from 'react-loading';
import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { NumCast, Cast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents, returnEmptyFilter } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents, returnEmptyFilter, returnEmptyDoclist } from "../../Utils";
import { Transform } from "../util/Transform";
import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
import { DocumentView } from "./nodes/DocumentView";
@@ -145,7 +146,7 @@ export class OverlayView extends React.Component {
@computed get overlayDocs() {
- const userDocOverlays = Doc.UserDoc().myOverlayDocuments;
+ const userDocOverlays = Doc.UserDoc().myOverlayDocs;
if (!userDocOverlays) {
return null;
}
@@ -165,7 +166,7 @@ export class OverlayView extends React.Component {
dragData.dropAction = "move";
dragData.removeDocument = (doc: Doc | Doc[]) => {
const docs = (doc instanceof Doc) ? [doc] : doc;
- docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null), "data", d));
+ docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocs, Doc, null), "data", d));
return true;
};
dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
@@ -205,12 +206,18 @@ export class OverlayView extends React.Component {
addDocTab={returnFalse}
pinToPres={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
});
}
+ public static ShowSpinner() {
+ return OverlayView.Instance.addElement(<ReactLoading type="spinningBubbles" color="green" height={250} width={250} />, { x: 300, y: 200 });
+ }
+
+
render() {
return (
<div className="overlayView" id="overlayView">
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 0a4334302..92c3f09b4 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc } from "../../fields/Doc";
import { NumCast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue, returnEmptyFilter } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue, returnEmptyFilter, returnEmptyDoclist } from "../../Utils";
import { Transform } from "../util/Transform";
import { DocumentView } from "./nodes/DocumentView";
import "./Palette.scss";
@@ -60,6 +60,7 @@ export default class Palette extends React.Component<PaletteProps> {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
<div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index d7034fcfb..1cadba18a 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -8,7 +8,7 @@ import { Doc } from '../../fields/Doc';
import { Transform } from "../util/Transform";
import { DocServer } from '../DocServer';
import { undoBatch, UndoManager } from '../util/UndoManager';
-import { NumCast } from '../../fields/Types';
+import { NumCast, Cast } from '../../fields/Types';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import * as rp from 'request-promise';
import { Utils } from '../../Utils';
@@ -44,8 +44,7 @@ export class PreviewCursor extends React.Component<{}> {
if (plain.indexOf("www.youtube.com/watch") !== -1) {
const url = plain.replace("youtube.com/watch?v=", "youtube.com/embed/");
undoBatch(() => PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
- title: url, _width: 400, _height: 315,
- _nativeWidth: 600, _nativeHeight: 472.5,
+ title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5,
x: newPoint[0], y: newPoint[1]
})))();
}
@@ -53,45 +52,41 @@ export class PreviewCursor extends React.Component<{}> {
else if (re.test(plain)) {
const url = plain;
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]
+ title: url, _width: 500, _height: 300, useCors: true, x: newPoint[0], y: newPoint[1]
})))();
}
-
else if (plain.startsWith("__DashDocId(") || plain.startsWith("__DashCloneId(")) {
const clone = plain.startsWith("__DashCloneId(");
const docids = plain.split(":");
const strs = docids[0].split(",");
const ptx = Number(strs[0].substring((clone ? "__DashCloneId(" : "__DashDocId(").length));
const pty = Number(strs[1].substring(0, strs[1].length - 1));
- let count = 1;
- const list: Doc[] = [];
- let first: Doc | undefined;
- docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
- count++;
- if (doc instanceof Doc) {
- i === 1 && (first = doc);
- const alias = clone ? (await Doc.MakeClone(doc)).clone : 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))();
- }
- }));
+ const batch = UndoManager.StartBatch("cloning");
+ {
+ const docs = await Promise.all(docids.filter((did, i) => i).map(async (did) => {
+ const doc = Cast(await DocServer.GetRefField(did), Doc, null);
+ return clone ? (await Doc.MakeClone(doc)).clone : doc;
+ }));
+ const firstx = docs.length ? NumCast(docs[0].x) + ptx - newPoint[0] : 0;
+ const firsty = docs.length ? NumCast(docs[0].y) + pty - newPoint[1] : 0;
+ docs.map(doc => {
+ doc.x = NumCast(doc.x) - firstx;
+ doc.y = NumCast(doc.y) - firsty;
+ });
+ PreviewCursor._addDocument(docs);
+ }
+ batch.end();
e.stopPropagation();
- } else {
+ }
+ else {
// creates text document
FormattedTextBox.PasteOnLoad = e;
undoBatch(() => PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
_width: 500,
limitHeight: 400,
_autoHeight: true,
+ _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
x: newPoint[0],
y: newPoint[1],
title: "-pasted text-"
@@ -132,6 +127,7 @@ export class PreviewCursor extends React.Component<{}> {
e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" &&
e.key !== "NumLock" && e.key !== " " &&
(e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
+ (e.keyCode < 173 || e.keyCode > 183 || e.key === "-") && // mute, volume up/down etc, - is there specifically because its keycode is 173 in Firefox so shouldn't be avoided
!e.key.startsWith("Arrow") &&
!e.defaultPrevented) {
if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index 8d9d56c9e..d63eb874c 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -43,6 +43,13 @@ $linkGap : 3px;
cursor: pointer;
}
}
+.propertiesButtons-linkButton-empty.toggle-on {
+ background-color: white;
+ color: black;
+}
+.propertiesButtons-linkButton-empty.toggle-off {
+ color: white;
+}
.propertiesButtons {
margin-top: 3px;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 35d4f7f6e..d66cba710 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,51 +1,29 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from '@material-ui/core';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
+import { InkField } from '../../fields/InkField';
import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast, BoolCast } from "../../fields/Types";
-import { emptyFunction, setupMoveUpEvents, Utils } from "../../Utils";
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { Cast, NumCast } from "../../fields/Types";
+import { ImageField } from '../../fields/URLField';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils';
import { Docs, DocUtils } from '../documents/Documents';
-import { DragManager } from '../util/DragManager';
-import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
-import { ParentDocSelector } from './collections/ParentDocumentSelector';
-import './collections/ParentDocumentSelector.scss';
-import './PropertiesButtons.scss';
-import { MetadataEntryMenu } from './MetadataEntryMenu';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentType } from '../documents/DocumentTypes';
+import { SelectionManager } from '../util/SelectionManager';
+import { undoBatch } from '../util/UndoManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
-import { TemplateMenu } from "./TemplateMenu";
-import { Template, Templates } from "./Templates";
+import './PropertiesButtons.scss';
import React = require("react");
-import { Tooltip } from '@material-ui/core';
-import { SelectionManager } from '../util/SelectionManager';
-import SharingManager from '../util/SharingManager';
-import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils';
-import { ImageField } from '../../fields/URLField';
-import { undoBatch, UndoManager } from '../util/UndoManager';
-import { DocumentType } from '../documents/DocumentTypes';
-import { InkField } from '../../fields/InkField';
-import { PresBox } from './nodes/PresBox';
+import { CollectionViewType } from './collections/CollectionView';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-library.add(faLink);
-library.add(faTag);
-library.add(faTimes);
-library.add(faArrowAltCircleDown);
-library.add(faArrowAltCircleUp);
-library.add(faArrowAltCircleRight);
-library.add(faStopCircle);
-library.add(faCheckCircle);
-library.add(faCloudUploadAlt);
-library.add(faSyncAlt);
-library.add(faShare);
-library.add(faPhotoVideo);
const cloud: IconProp = "cloud-upload-alt";
const fetch: IconProp = "sync-alt";
@@ -58,7 +36,6 @@ enum UtilityButtonState {
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
- private _dragRef = React.createRef<HTMLDivElement>();
private _pullAnimating = false;
private _pushAnimating = false;
private _pullColorAnimating = false;
@@ -76,13 +53,12 @@ export class PropertiesButtons extends React.Component<{}, {}> {
public static hasPulledHack = false;
+ @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
@computed get selectedDocumentView() {
if (SelectionManager.SelectedDocuments().length) {
return SelectionManager.SelectedDocuments()[0];
- } else { return undefined; }
+ } else return undefined;
}
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
- @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; }
@computed get onClick() { return this.selectedDoc?.onClickBehavior ? this.selectedDoc?.onClickBehavior : "nothing"; }
@@ -167,11 +143,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
<div className="propertiesButtons-linker"
style={{ backgroundColor: this.pullColor }}
onPointerEnter={action(e => {
- if (e.altKey) {
- this.openHover = UtilityButtonState.OpenExternally;
- } else if (e.shiftKey) {
- this.openHover = UtilityButtonState.OpenRight;
- }
+ e.altKey && (this.openHover = UtilityButtonState.OpenExternally);
+ e.shiftKey && (this.openHover = UtilityButtonState.OpenRight);
})}
onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
onClick={async e => {
@@ -180,11 +153,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
e.preventDefault();
let googleDoc = await Cast(dataDoc.googleDoc, Doc);
if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, useCors: false };
googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
dataDoc.googleDoc = googleDoc;
}
- CollectionDockingView.AddRightSplit(googleDoc);
+ CollectionDockingView.AddSplit(googleDoc, "right");
} else if (e.altKey) {
e.preventDefault();
window.open(googleDocUrl);
@@ -195,8 +168,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
}
}}>
- <FontAwesomeIcon className="documentdecorations-icon" size="lg"
- color="black"
+ <FontAwesomeIcon className="documentdecorations-icon" size="lg" color="black"
style={{ WebkitAnimation: animation, MozAnimation: animation }}
icon={(() => {
switch (this.openHover) {
@@ -212,248 +184,116 @@ export class PropertiesButtons extends React.Component<{}, {}> {
</div>
</Tooltip>;
}
+
+ @action @undoBatch
+ onLock = () => {
+ SelectionManager.SelectedDocuments().forEach(dv => dv.toggleLockPosition());
+ }
+
@computed
- get pinButton() {
+ get lockButton() {
const targetDoc = this.selectedDoc;
- const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
+ return !targetDoc ? (null) : <Tooltip
+ title={<div className="dash-tooltip">{`${this.selectedDoc?.lockedPosition ? "Unlock" : "Lock"} " Position"`}</div>} placement="top">
<div>
- <div className="propertiesButtons-linker"
- style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white" }}
- onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" />
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc.lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} >
+ <FontAwesomeIcon className="documentdecorations-icon" size="lg"
+ color={this.selectedDoc?.lockedPosition ? "black" : "white"}
+ icon={this.selectedDoc?.lockedPosition ? "unlock" : "lock"} />
</div>
-
<div className="propertiesButtons-title"
- // style={{
- // backgroundColor: Doc.isDocPinned(targetDoc) ? "white" : "black",
- // color: Doc.isDocPinned(targetDoc) ? "black" : "white"
- // }}
- >{Doc.isDocPinned(targetDoc) ? "Unpin" : "Pin"}</div>
+ >Position </div>
</div>
</Tooltip>;
}
@computed
- get pinWithViewButton() {
+ get downloadButton() {
const targetDoc = this.selectedDoc;
- if (targetDoc) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- }
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with this view"}</div></>} placement="top">
+ return !targetDoc ? (null) : <Tooltip
+ title={<div className="dash-tooltip">{"Download Document"}</div>} placement="top">
<div>
- <div className="propertiesButtons-linker"
- onClick={e => {
- if (targetDoc) {
- DockedFrameRenderer.PinDoc(targetDoc, false);
- const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeDoc.presPinView = true;
- activeDoc.presPinViewX = x;
- activeDoc.presPinViewY = y;
- activeDoc.presPinViewScale = scale;
- }
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" />
- <div style={{ position: 'relative', fontSize: 25, fontWeight: 700, transform: 'translate(0, -28px)', color: 'rgba(250,250,250,0.55)' }}>V</div>
+ <div className={"propertiesButtons-linkButton-empty"} onPointerDown={() => this.selectedDoc && Doc.Zip(this.selectedDoc)}>
+ <FontAwesomeIcon className="propertiesButtons-icon" icon="download" size="lg" />
</div>
-
- <div className="propertiesButtons-title">{"View"}</div>
+ <div className="propertiesButtons-title"> downld </div>
</div>
</Tooltip>;
}
-
- @computed
- get metadataButton() {
- //const view0 = this.view0;
- if (this.selectedDoc) {
- return <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>} placement="top">
- <div className="propertiesButtons-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP}
- content={<MetadataEntryMenu docs={[this.selectedDoc]} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
- <div>
- <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="lg" />}
- </div>
- <div className="propertiesButtons-title">Metadata</div>
- </div>
- </Flyout>
- </div></Tooltip>;
- } else {
- return null;
- }
-
- }
-
- @observable _aliasDown = false;
- onAliasButtonDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
- }
@undoBatch
- onAliasButtonMoved = () => {
- if (this._dragRef.current) {
- const dragDocView = this.selectedDocumentView!;
- const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
- const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- dragData.dropAction = "alias";
- DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
- offsetX: dragData.offset[0],
- offsetY: dragData.offset[1],
- hideSource: false
- });
- return true;
- }
- return false;
- }
-
- @computed
- get templateButton() {
- const docView = this.selectedDocumentView;
- const templates: Map<Template, boolean> = new Map();
- const views = [this.selectedDocumentView];
- Array.from(Object.values(Templates.TemplateList)).map(template =>
- templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
- return !docView ? (null) :
- <Tooltip title={<><div className="dash-tooltip">Customize layout</div></>} placement="top">
- <div className="propertiesButtons-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} //onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
- content={<TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
- <div>
- <div className={"propertiesButtons-linkButton-empty"} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="lg" />}
- </div>
- <div className="propertiesButtons-title">Layout</div>
- </div>
- </Flyout>
- </div></Tooltip>;
- }
-
- @undoBatch
- onCopy = () => {
- if (this.selectedDoc && this.selectedDocumentView) {
- // const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true);
- // copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
- // copy.y = NumCast(this.selectedDoc.y) + 30;
- // this.selectedDocumentView.props.addDocument?.(copy);
- const alias = Doc.MakeAlias(this.selectedDoc);
- alias.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
- alias.y = NumCast(this.selectedDoc.y) + 30;
- this.selectedDocumentView.props.addDocument?.(alias);
- }
+ @action
+ setDictation = () => {
+ SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._showAudio = dv.rootDoc._showAudio === !dv.rootDoc._showAudio);
}
@computed
- get copyButton() {
+ get dictationButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Tap or Drag to create an alias"}</div></>} placement="top">
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Dictation Controls"}</div>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- ref={this._dragRef}
- onPointerDown={this.onAliasButtonDown}
- onClick={this.onCopy}>
- {<FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="lg" />}
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showAudio ? "on" : "off"}`} onPointerDown={this.setDictation}>
+ <FontAwesomeIcon className="propertiesButtons-icon" icon="microphone" size="lg" />
</div>
- <div className="propertiesButtons-title">Alias</div>
+ <div className="propertiesButtons-title"> Dictate </div>
</div>
</Tooltip>;
}
- @action @undoBatch
- onLock = () => {
- this.selectedDocumentView?.toggleLockPosition();
+
+ @undoBatch
+ @action
+ setTitle = () => {
+ SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._showTitle = dv.rootDoc._showTitle === undefined ? "title" : undefined);
}
@computed
- get lockButton() {
+ get titleButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{this.selectedDoc?.lockedPosition ?
- "Unlock Position" : "Lock Position"}</div></>} placement="top">
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Title Header"}</div>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- style={{ backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "" }}
- onPointerDown={this.onLock} >
- {<FontAwesomeIcon className="documentdecorations-icon"
- color={BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white"}
- icon={BoolCast(this.selectedDoc?.lockedPosition) ? "unlock" : "lock"} size="lg" />}
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showTitle ? "on" : "off"}`} onPointerDown={this.setTitle}>
+ <FontAwesomeIcon className="propertiesButtons-icon" icon="text-width" size="lg" />
</div>
- <div className="propertiesButtons-title"
- // style={{
- // backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "black",
- // color: BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white"
- // }}
- >Position </div>
+ <div className="propertiesButtons-title"> Title </div>
</div>
</Tooltip>;
}
- @computed
- get downloadButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{"Download Document"}</div></>} placement="top">
- <div>
- <div className={"propertiesButtons-linkButton-empty"}
- onPointerDown={async () => {
- if (this.selectedDocumentView?.props.Document) {
- Doc.Zip(this.selectedDocumentView?.props.Document);
- }
- }}>
- {<FontAwesomeIcon className="propertiesButtons-icon"
- icon="download" size="lg" />}
- </div>
- <div className="propertiesButtons-title"> downld </div>
- </div>
- </Tooltip>;
+ @undoBatch
+ @action
+ setCaption = () => {
+ SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined);
}
@computed
- get deleteButton() {
+ get captionButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">Close Document</div></>} placement="top">
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Caption Footer"}</div>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- onPointerDown={this.deleteDocument}>
- {<FontAwesomeIcon className="propertiesButtons-icon"
- icon="times" size="lg" />}
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showCaption ? "on" : "off"}`} onPointerDown={this.setCaption}>
+ <FontAwesomeIcon className="propertiesButtons-icon" icon="closed-captioning" size="lg" />
</div>
- <div className="propertiesButtons-title"> close </div>
+ <div className="propertiesButtons-title"> Caption </div>
</div>
</Tooltip>;
}
@undoBatch
@action
- deleteDocument = () => {
- const selected = SelectionManager.SelectedDocuments().slice();
- selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
- this.selectedDoc && (this.selectedDoc.deleted = true);
- this.selectedDocumentView?.props.ContainingCollectionView?.removeDocument(this.selectedDocumentView?.props.Document);
- SelectionManager.DeselectAll();
+ setChrome = () => {
+ SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._chromeStatus = dv.rootDoc._chromeStatus === "disabled" ? "enabled" : "disabled");
}
@computed
- get sharingButton() {
+ get chromeButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{"Share Document"}</div></>} placement="top">
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Editing UI"}</div>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- onPointerDown={() => {
- if (this.selectedDocumentView) {
- SharingManager.Instance.open(this.selectedDocumentView);
- }
- }}>
- {<FontAwesomeIcon className="propertiesButtons-icon"
- icon="users" size="lg" />}
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._chromeStatus === "enabled" ? "on" : "off"}`} onPointerDown={this.setChrome}>
+ <FontAwesomeIcon className="propertiesButtons-icon" icon="edit" size="lg" />
</div>
- <div className="propertiesButtons-title"> share </div>
+ <div className="propertiesButtons-title"> Controls </div>
</div>
</Tooltip>;
}
@@ -484,27 +324,31 @@ export class PropertiesButtons extends React.Component<{}, {}> {
handleOptionChange = (e: any) => {
const value = e.target.value;
this.selectedDoc && (this.selectedDoc.onClickBehavior = e.target.value);
- if (value === "nothing") {
- this.selectedDocumentView?.noOnClick();
- } else if (value === "enterPortal") {
- this.selectedDocumentView?.noOnClick();
- this.selectedDocumentView?.makeIntoPortal();
- } else if (value === "toggleDetail") {
- this.selectedDocumentView?.noOnClick();
- this.selectedDocumentView?.toggleDetail();
- } else if (value === "linkInPlace") {
- this.selectedDocumentView?.noOnClick();
- this.selectedDocumentView?.toggleFollowLink("inPlace", true, false);
- } else if (value === "linkOnRight") {
- this.selectedDocumentView?.noOnClick();
- this.selectedDocumentView?.toggleFollowLink("onRight", false, false);
- }
+
+ SelectionManager.SelectedDocuments().forEach(dv => {
+ if (value === "nothing") {
+ dv.noOnClick();
+ } else if (value === "enterPortal") {
+ dv.noOnClick();
+ dv.makeIntoPortal();
+ } else if (value === "toggleDetail") {
+ dv.noOnClick();
+ dv.toggleDetail();
+ } else if (value === "linkInPlace") {
+ dv.noOnClick();
+ dv.toggleFollowLink("inPlace", true, false);
+ } else if (value === "linkOnRight") {
+ dv.noOnClick();
+ dv.toggleFollowLink("add:right", false, false);
+ }
+ });
}
@undoBatch @action
editOnClickScript = () => {
if (this.selectedDoc) {
- DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick");
+ if (SelectionManager.SelectedDocuments().length) SelectionManager.SelectedDocuments().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, "onClick"));
+ else DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick");
}
}
@@ -563,13 +407,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
title={<><div className="dash-tooltip">{"Export to Google Photos"}</div></>} placement="top">
<div>
<div className={"propertiesButtons-linkButton-empty"}
- onPointerDown={() => {
- if (this.selectedDocumentView) {
- GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDocumentView.Document }).then(console.log);
- }
- }}>
- {<FontAwesomeIcon className="documentdecorations-icon"
- icon="cloud-upload-alt" size="lg" />}
+ onPointerDown={() => this.selectedDoc && GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDoc }).then(console.log)}>
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="cloud-upload-alt" size="lg" />}
</div>
<div className="propertiesButtons-title"> google </div>
</div>
@@ -580,33 +419,30 @@ export class PropertiesButtons extends React.Component<{}, {}> {
get clustersButton() {
const targetDoc = this.selectedDoc;
return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{this.selectedDoc?.useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>} placement="top">
+ title={<><div className="dash-tooltip">{this.selectedDoc?._useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- style={{ backgroundColor: this.selectedDoc?.useClusters ? "white" : "" }}
- onPointerDown={this.changeClusters}>
- {<FontAwesomeIcon className="documentdecorations-icon"
- color={this.selectedDoc?.useClusters ? "black" : "white"}
- icon="braille" size="lg" />}
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._useClusters ? "on" : "off"}`} onPointerDown={this.changeClusters}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="braille" size="lg" />
</div>
- <div className="propertiesButtons-title"
- // style={{
- // backgroundColor: this.selectedDoc?.useClusters ? "white" : "black",
- // color: this.selectedDoc?.useClusters ? "black" : "white"
- // }}
- > clusters </div>
+ <div className="propertiesButtons-title" > clusters </div>
</div>
</Tooltip>;
}
@action @undoBatch
changeFitToBox = () => {
- this.selectedDoc && (this.selectedDoc._fitToBox = !this.selectedDoc._fitToBox);
+ if (this.selectedDoc) {
+ if (SelectionManager.SelectedDocuments().length) SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._fitToBox = !dv.rootDoc._fitToBox);
+ else this.selectedDoc._fitToBox = !this.selectedDoc._fitToBox;
+ }
}
@action @undoBatch
changeClusters = () => {
- this.selectedDoc && (this.selectedDoc.useClusters = !this.selectedDoc.useClusters);
+ if (this.selectedDoc) {
+ if (SelectionManager.SelectedDocuments().length) SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._useClusters = !dv.rootDoc._useClusters);
+ else this.selectedDoc._useClusters = !this.selectedDoc._useClusters;
+ }
}
@computed
@@ -615,19 +451,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return !targetDoc ? (null) : <Tooltip
title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- style={{ backgroundColor: this.selectedDoc?._fitToBox ? "white" : "" }}
- onPointerDown={this.changeFitToBox}>
- {<FontAwesomeIcon className="documentdecorations-icon"
- color={this.selectedDoc?._fitToBox ? "black" : "white"}
- icon="expand" size="lg" />}
+ <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._fitToBox ? "on" : "off"}`} onPointerDown={this.changeFitToBox}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="expand" size="lg" />
</div>
- <div className="propertiesButtons-title"
- // style={{
- // backgroundColor: this.selectedDoc?._fitToBox ? "white" : "black",
- // color: this.selectedDoc?._fitToBox ? "black" : "white"
- // }}
- > {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div>
+ <div className="propertiesButtons-title"> {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div>
</div>
</Tooltip>;
}
@@ -650,101 +477,45 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return !targetDoc ? (null) : <Tooltip
title={<><div className="dash-tooltip">Make Mask</div></>} placement="top">
<div>
- <div className={"propertiesButtons-linkButton-empty"}
- onPointerDown={this.makeMask}>
- {<FontAwesomeIcon className="documentdecorations-icon"
- color="white" icon="paint-brush" size="lg" />}
+ <div className={"propertiesButtons-linkButton-empty"} onPointerDown={this.makeMask}>
+ <FontAwesomeIcon className="documentdecorations-icon" color="white" icon="paint-brush" size="lg" />
</div>
<div className="propertiesButtons-title"> mask </div>
</div>
</Tooltip>;
}
- @computed
- get contextButton() {
- if (this.selectedDoc) {
- return <Tooltip title={<><div className="dash-tooltip">Show Context</div></>} placement="top">
- <div>
- <div className={"propertiesButtons-linkButton-empty"}>
- <ParentDocSelector Document={this.selectedDoc} addDocTab={(doc, where) => {
- where === "onRight" ? CollectionDockingView.AddRightSplit(doc) :
- this.selectedDocumentView?.props.addDocTab(doc, "onRight");
- return true;
- }} />
- </div>
- <div className="propertiesButtons-title"> context </div>
- </div>
- </Tooltip>;
- } else {
- return false;
- }
-
- }
-
- // @computed
- // get importButton() {
- // const targetDoc = this.selectedDoc;
- // return !targetDoc ? (null) : <Tooltip
- // title={<><div className="dash-tooltip">{"Import a Document"}</div></>}>
- // <div className={"propertiesButtons-linkButton-empty"}
- // onPointerDown={() => {
- // if (this.selectedDocumentView) {
- // CollectionFreeFormView.importDocument(100, 100);
- // }
- // }}>
- // {<FontAwesomeIcon className="documentdecorations-icon"
- // icon="upload" size="sm" />}
- // </div>
- // </Tooltip>;
- // }
-
-
render() {
if (!this.selectedDoc) return (null);
- const isText = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField;
+ const layoutField = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)];
+ const isText = layoutField instanceof RichTextField;
+ const isImage = layoutField instanceof ImageField;
+ const isInk = layoutField instanceof InkField;
+ const isCollection = this.selectedDoc.type === DocumentType.COL;
+ const isFreeForm = this.selectedDoc._viewType === CollectionViewType.Freeform;
const considerPull = isText && this.considerGoogleDocsPull;
const considerPush = isText && this.considerGoogleDocsPush;
- const isImage = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof ImageField;
- const isInk = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof InkField;
- const isCollection = this.selectedDoc.type === DocumentType.COL ? true : false;
- const isFreeForm = this.selectedDoc._viewType === "freeform" ? true : false;
- const hasContext = this.selectedDoc.context ? true : false;
- return <div><div className="propertiesButtons" style={{ paddingBottom: "5.5px" }}>
+ return <div className="propertiesButtons" style={{ paddingBottom: "5.5px" }}>
<div className="propertiesButtons-button">
- {this.templateButton}
+ {this.titleButton}
</div>
- {/* <div className="propertiesButtons-button">
- {this.metadataButton}
- </div> */}
<div className="propertiesButtons-button">
- {this.pinButton}
+ {this.captionButton}
</div>
- <div className="propertiesButtons-button">
- {this.pinWithViewButton}
- </div>
- <div className="propertiesButtons-button" style={{ display: hasContext ? "" : "none" }}>
- {this.copyButton}
+ <div className="propertiesButtons-button" style={{ display: isCollection ? "" : "none" }}>
+ {this.chromeButton}
</div>
<div className="propertiesButtons-button">
{this.lockButton}
</div>
- <div className="propertiesButtons-button">
- {this.downloadButton}
- </div>
- <div className="propertiesButtons-button">
- {this.deleteButton}
+ <div className="propertiesButtons-button" style={{ display: isText || isImage ? "" : "none" }}>
+ {this.dictationButton}
</div>
<div className="propertiesButtons-button">
{this.onClickButton}
</div>
- <div className="propertiesButtons-button">
- {this.sharingButton}
- </div>
- <div className="propertiesButtons-button">
- {this.contextButton}
- </div>
<div className="propertiesButtons-button" style={{ display: !considerPush ? "none" : "" }}>
{this.considerGoogleDocsPush}
</div>
@@ -754,22 +525,16 @@ export class PropertiesButtons extends React.Component<{}, {}> {
<div className="propertiesButtons-button" style={{ display: !isImage ? "none" : "" }}>
{this.googlePhotosButton}
</div>
- {/* <div className="propertiesButtons-button" style={{ display: !isCollection ? "none" : "" }}>
- {this.importButton}
- </div> */}
<div className="propertiesButtons-button" style={{ display: !isFreeForm ? "none" : "" }}>
{this.clustersButton}
</div>
-
<div className="propertiesButtons-button" style={{ display: !isFreeForm ? "none" : "" }}>
{this.fitContentButton}
</div>
-
<div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}>
{this.maskButton}
</div>
- </div>
</div>;
}
}
diff --git a/src/client/views/PropertiesDocContextSelector.scss b/src/client/views/PropertiesDocContextSelector.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/PropertiesDocContextSelector.scss
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
new file mode 100644
index 000000000..427748fe7
--- /dev/null
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -0,0 +1,58 @@
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { NumCast, StrCast } from "../../fields/Types";
+import { CollectionViewType } from "./collections/CollectionView";
+import { CollectionDockingView } from "./collections/CollectionDockingView";
+import './PropertiesDocContextSelector.scss';
+import { SearchUtil } from "../util/SearchUtil";
+
+type PropertiesDocContextSelectorProps = {
+ Document: Doc,
+ Stack?: any,
+ hideTitle?: boolean,
+ addDocTab(doc: Doc, location: string): void
+};
+
+@observer
+export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
+ @observable private _docs: { col: Doc, target: Doc }[] = [];
+ @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
+ _reaction: IReactionDisposer | undefined;
+
+ componentDidMount() { this._reaction = reaction(() => this.props.Document, () => this.fetchDocuments(), { fireImmediately: true }); }
+ componentWillUnmount() { this._reaction?.(); }
+ async fetchDocuments() {
+ const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+ const containerProtoSets = await Promise.all(aliases.map(async alias => ((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
+ const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+ const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => SearchUtil.GetAliasesOfDocument(container)));
+ const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+ const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => SearchUtil.GetAliasesOfDocument(dp)));
+ const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
+ runInAction(() => {
+ this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).filter(doc => !Doc.IsSystem(doc)).map(doc => ({ col: doc, target: this.props.Document }));
+ this._otherDocs = [];
+ });
+ }
+
+ getOnClick = (col: Doc, target: Doc) => {
+ col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+ if (col._viewType === CollectionViewType.Freeform) {
+ col._panX = NumCast(target.x) + NumCast(target._width) / 2;
+ col._panY = NumCast(target.y) + NumCast(target._height) / 2;
+ }
+ this.props.addDocTab(col, "add:right");
+ }
+
+ render() {
+ return <div>
+ {this.props.hideTitle ? (null) : <p key="contexts">Contexts:</p>}
+ {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a></p>)}
+ {this._otherDocs.length ? <hr key="hr" /> : null}
+ {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a></p>)}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/PropertiesView.scss
index aee28366a..e5f9e0417 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -6,7 +6,7 @@
cursor: auto;
overflow-x: hidden;
- overflow-y: scroll;
+ overflow-y: auto;
.propertiesView-title {
background-color: rgb(159, 159, 159);
@@ -47,7 +47,7 @@
}
.propertiesView-settings {
- border-bottom: 1px solid black;
+ //border-bottom: 1px solid black;
//padding: 8.5px;
font-size: 12.5px;
font-weight: bold;
@@ -87,7 +87,7 @@
}
.propertiesView-sharing {
- border-bottom: 1px solid black;
+ //border-bottom: 1px solid black;
//padding: 8.5px;
.propertiesView-sharing-title {
@@ -121,6 +121,19 @@
padding: 10px;
margin-left: 5px;
+ .propertiesView-acls-checkbox {
+ float: right;
+ height: 20px;
+ margin-top: -20px;
+ margin-right: -15;
+
+ .propertiesView-acls-checkbox-text {
+ font-size: 7px;
+ margin-top: -10px;
+ margin-left: 6px;
+ }
+ }
+
.change-buttons {
display: flex;
@@ -137,7 +150,7 @@
}
.propertiesView-appearance {
- border-bottom: 1px solid black;
+ //border-bottom: 1px solid black;
//padding: 8.5px;
.propertiesView-appearance-title {
@@ -174,7 +187,7 @@
}
.propertiesView-transform {
- border-bottom: 1px solid black;
+ //border-bottom: 1px solid black;
//padding: 8.5px;
.propertiesView-transform-title {
@@ -258,7 +271,8 @@
// display: inline-table;
background-color: #ececec;
max-height: 130px;
- overflow-y: scroll;
+ overflow-y: auto;
+ width: 92%;
.propertiesView-sharingTable-item {
@@ -267,7 +281,6 @@
padding: 3px;
align-items: center;
border-bottom: 0.5px solid grey;
- cursor: pointer;
&:hover .propertiesView-sharingTable-item-name {
overflow-x: unset;
@@ -290,7 +303,6 @@
margin-left: auto;
.permissions-select {
- z-index: 1;
border: none;
background-color: inherit;
width: 75px;
@@ -310,7 +322,7 @@
}
.propertiesView-fields {
- border-bottom: 1px solid black;
+ //border-bottom: 1px solid black;
//padding: 8.5px;
.propertiesView-fields-title {
@@ -383,6 +395,41 @@
}
}
+ .propertiesView-contexts {
+
+ .propertiesView-contexts-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-contexts-title-icon {
+ float: right;
+ justify-items: right;
+ align-items: flex-end;
+ margin-left: auto;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-contexts-content {
+ overflow: hidden;
+ padding: 10px;
+ }
+
+ }
+
.propertiesView-layout {
.propertiesView-layout-title {
@@ -419,7 +466,7 @@
}
.propertiesView-presTrails {
- border-bottom: 1px solid black;
+ //border-bottom: 1px solid black;
//padding: 8.5px;
.propertiesView-presTrails-title {
@@ -718,7 +765,9 @@
border: none;
padding: 6px;
padding-bottom: 2px;
-
+ background: #eeeeee;
+ border-top: 1px solid;
+ border-left: 1px solid;
&:hover {
border: 0.75px solid rgb(122, 28, 28);
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 57e968aa7..46f38795c 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,46 +1,40 @@
import React = require("react");
-import { observer } from "mobx-react";
-import "./PropertiesView.scss";
-import { observable, action, computed, runInAction } from "mobx";
-import { Doc, Field, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt, DocCastAsync } from "../../../../fields/Doc";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { EditableView } from "../../EditableView";
-import { KeyValueBox } from "../../nodes/KeyValueBox";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentView";
-import { returnFalse, returnOne, emptyFunction, emptyPath, returnTrue, returnZero, returnEmptyFilter, Utils } from "../../../../Utils";
-import { Id } from "../../../../fields/FieldSymbols";
-import { Transform } from "../../../util/Transform";
-import { PropertiesButtons } from "../../PropertiesButtons";
-import { SelectionManager } from "../../../util/SelectionManager";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip, Checkbox } from "@material-ui/core";
-import SharingManager from "../../../util/SharingManager";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { SharingPermissions, GetEffectiveAcl } from "../../../../fields/util";
-import { InkField } from "../../../../fields/InkField";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { Checkbox, Tooltip } from "@material-ui/core";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
-import "./FormatShapePane.scss";
-import { PresBox } from "../../nodes/PresBox";
-import { DocumentManager } from "../../../util/DocumentManager";
-import FormatShapePane from "./FormatShapePane";
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { InkField } from "../../fields/InkField";
+import { ComputedField } from "../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
+import { GetEffectiveAcl, SharingPermissions } from "../../fields/util";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../Utils";
+import { DocumentType } from "../documents/DocumentTypes";
+import { DocumentManager } from "../util/DocumentManager";
+import { SelectionManager } from "../util/SelectionManager";
+import { SharingManager } from "../util/SharingManager";
+import { Transform } from "../util/Transform";
+import { undoBatch, UndoManager } from "../util/UndoManager";
+import { CollectionDockingView } from "./collections/CollectionDockingView";
+import { EditableView } from "./EditableView";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { ContentFittingDocumentView } from "./nodes/ContentFittingDocumentView";
+import { KeyValueBox } from "./nodes/KeyValueBox";
+import { PresBox } from "./nodes/PresBox";
+import { PropertiesButtons } from "./PropertiesButtons";
+import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
+import "./PropertiesView.scss";
+import { intersection } from "lodash";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
const _global = (window /* browser */ || global /* node */) as any;
-// import * as fa from '@fortawesome/free-solid-svg-icons';
-// import { library } from "@fortawesome/fontawesome-svg-core";
-
-// library.add(fa.faPlus, fa.faMinus, fa.faCog);
-
interface PropertiesViewProps {
width: number;
height: number;
- renderDepth: number;
- ScreenToLocalTransform: () => Transform;
- onDown: (event: any) => void;
}
@observer
@@ -49,30 +43,30 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get MAX_EMBED_HEIGHT() { return 200; }
+ @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
@computed get selectedDocumentView() {
- if (SelectionManager.SelectedDocuments().length) {
- return SelectionManager.SelectedDocuments()[0];
- } else if (PresBox.Instance && PresBox.Instance._selectedArray.length) {
- return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
- } else { return undefined; }
+ if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0];
+ if (PresBox.Instance && PresBox.Instance._selectedArray.length) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+ return undefined;
}
@computed get isPres(): boolean {
if (this.selectedDoc?.type === DocumentType.PRES) return true;
return false;
}
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
- @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; }
+ @computed get dataDoc() { return this.selectedDoc?.[DataSym]; }
@observable layoutFields: boolean = false;
- @observable openActions: boolean = true;
+ @observable openOptions: boolean = true;
@observable openSharing: boolean = true;
@observable openFields: boolean = true;
@observable openLayout: boolean = true;
+ @observable openContexts: boolean = true;
@observable openAppearance: boolean = true;
@observable openTransform: boolean = true;
// @observable selectedUser: string = "";
// @observable addButtonPressed: boolean = false;
+ @observable layoutDocAcls: boolean = false;
//Pres Trails booleans:
@observable openPresTransitions: boolean = false;
@@ -80,7 +74,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openAddSlide: boolean = false;
@observable openSlideOptions: boolean = false;
- @observable inActions: boolean = false;
+ @observable inOptions: boolean = false;
@observable _controlBtn: boolean = false;
@observable _lock: boolean = false;
@@ -135,24 +129,26 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get expandedField() {
if (this.dataDoc && this.selectedDoc) {
const ids: { [key: string]: string } = {};
- const doc = this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc;
- doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+ const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] :
+ SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc);
+ docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
const rows: JSX.Element[] = [];
for (const key of Object.keys(ids).slice().sort()) {
- const contents = doc[key];
+ const docvals = new Set<any>();
+ docs.forEach(doc => docvals.add(doc[key]));
+ const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
if (key[0] === "#") {
rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "2px" }} key={key}>
<span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
&nbsp;
</div>);
} else {
- let contentElement: (JSX.Element | null)[] | JSX.Element = [];
- contentElement = <EditableView key="editableView"
+ const contentElement = <EditableView key="editableView"
contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
height={13}
fontSize={10}
- GetValue={() => Field.toKeyValueString(doc, key)}
- SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)}
+ GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
+ SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
/>;
rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
<span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
@@ -175,45 +171,45 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get noviceFields() {
- if (this.dataDoc && this.selectedDoc) {
+ if (this.dataDoc) {
const ids: { [key: string]: string } = {};
- const doc = this.dataDoc;
- doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+ const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc);
+ docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
const rows: JSX.Element[] = [];
- for (const key of Object.keys(ids).slice().sort()) {
- if ((key[0] === key[0].toUpperCase() && key.substring(0, 3) !== "ACL" && key !== "UseCors")
- || key[0] === "#" || key === "author" ||
- key === "creationDate" || key.indexOf("lastModified") !== -1) {
-
- const contents = doc[key];
- if (key[0] === "#") {
+ const noviceReqFields = ["author", "creationDate", "tags"];
+ const noviceLayoutFields = ["_curPage"];
+ const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("ACL"))),
+ ...noviceReqFields, ...noviceLayoutFields];
+ for (const key of noviceKeys.sort()) {
+ const docvals = new Set<any>();
+ docs.forEach(doc => docvals.add(doc[key]));
+ const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
+ if (key[0] === "#") {
+ rows.push(<div className="uneditable-field" key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
+ &nbsp;
+ </div>);
+ } else if (contents !== undefined) {
+ const value = Field.toString(contents as Field);
+ if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) {
rows.push(<div className="uneditable-field" key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
- &nbsp;
- </div>);
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ": "}</span>
+ <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
+ </div>);
} else {
- const value = Field.toString(contents as Field);
- if (key === "author" || key === "creationDate" || key.indexOf("lastModified") !== -1) {
- rows.push(<div className="uneditable-field" key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ": "}</span>
- <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
- </div>);
- } else {
- let contentElement: (JSX.Element | null)[] | JSX.Element = [];
- contentElement = <EditableView key="editableView"
- contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
- height={13}
- fontSize={10}
- GetValue={() => Field.toKeyValueString(doc, key)}
- SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)}
- />;
-
- rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
- &nbsp;
- {contentElement}
- </div>);
- }
+ const contentElement = <EditableView key="editableView"
+ contents={value}
+ height={13}
+ fontSize={10}
+ GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
+ SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
+ />;
+
+ rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
+ &nbsp;
+ {contentElement}
+ </div>);
}
}
}
@@ -232,18 +228,27 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
setKeyValue = (value: string) => {
- if (this.selectedDoc && this.dataDoc) {
- const doc = this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc;
+ const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc);
+ docs.forEach(doc => {
if (value.indexOf(":") !== -1) {
const newVal = value[0].toUpperCase() + value.substring(1, value.length);
- KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
+ const splits = newVal.split(":");
+ KeyValueBox.SetField(doc, splits[0], splits[1], true);
+ const tags = StrCast(doc.tags, ":");
+ if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") {
+ KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true);
+ }
return true;
} else if (value[0] === "#") {
const newVal = value + `:'${value}'`;
- KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
+ doc[DataSym][value] = value;
+ const tags = StrCast(doc.tags, ":");
+ if (!tags.includes(`${value}:`)) {
+ doc[DataSym].tags = `${tags + value + ':'}`;
+ }
return true;
}
- }
+ });
return false;
}
@@ -257,8 +262,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
ref && observer.observe(ref);
}
+ @computed get contexts() {
+ return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
+ }
+
previewBackground = () => "lightgrey";
@computed get layoutPreview() {
+ if (SelectionManager.SelectedDocuments().length > 1) {
+ return "-- multiple selected --";
+ }
if (this.selectedDoc) {
const layoutDoc = Doc.Layout(this.selectedDoc);
const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
@@ -268,7 +280,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
Document={layoutDoc}
DataDoc={this.dataDoc}
LibraryPath={emptyPath}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={1}
rootSelected={returnFalse}
treeViewDoc={undefined}
backgroundColor={this.previewBackground}
@@ -283,6 +295,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
focus={returnFalse}
ScreenToLocalTransform={this.getTransform}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
addDocument={returnFalse}
@@ -308,19 +321,22 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
*/
@undoBatch
changePermissions = (e: any, user: string) => {
- SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!);
+ const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.selectedDoc!] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document);
+ SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
}
/**
* @returns the options for the permissions dropdown.
*/
getPermissionsSelect(user: string, permission: string) {
+ const dropdownValues: string[] = Object.values(SharingPermissions);
+ if (permission === "-multiple-") dropdownValues.unshift(permission);
return <select className="permissions-select"
value={permission}
onChange={e => this.changePermissions(e, user)}>
- {Object.values(SharingPermissions).map(permission => {
+ {dropdownValues.map(permission => {
return (
- <option key={permission} value={permission} selected={this.selectedDoc![`ACL-${user.replace(".", "_")}`] === permission}>
+ <option key={permission} value={permission}>
{permission}
</option>);
})}
@@ -344,8 +360,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get expansionIcon() {
return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}>
<div className="expansion-button" onPointerDown={() => {
- if (this.selectedDocumentView) {
- SharingManager.Instance.open(this.selectedDocumentView);
+ if (this.selectedDocumentView || this.selectedDoc) {
+ SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc);
}
}}>
<FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
@@ -356,7 +372,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
/**
* @returns a row of the permissions panel
*/
- sharingItem(name: string, effectiveAcl: symbol, permission: string) {
+ sharingItem(name: string, admin: boolean, permission: string) {
return <div className="propertiesView-sharingTable-item" key={name + permission}
// style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
// onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
@@ -364,7 +380,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "85px" : "80px" }}> {name} </div>
{/* {name !== "Me" ? this.notifyIcon : null} */}
<div className="propertiesView-sharingTable-item-permission">
- {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission}
+ {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission}
{permission === "Owner" ? this.expansionIcon : null}
</div>
</div>;
@@ -382,15 +398,29 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
[AclAdmin, SharingPermissions.Admin]
]);
- const effectiveAcl = GetEffectiveAcl(this.selectedDoc!);
+ // all selected docs
+ const docs = SelectionManager.SelectedDocuments().length < 2 ?
+ [this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym]]
+ : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]);
+
+ const target = docs[0];
+
+ // tslint:disable-next-line: no-unnecessary-callback-wrapper
+ const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
+ const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
+
+ // users in common between all docs
+ const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym])));
+
const tableEntries = [];
// DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
- if (this.selectedDoc![AclSym]) {
- for (const [key, value] of Object.entries(this.selectedDoc![AclSym])) {
+ if (commonKeys.length) {
+ for (const key of commonKeys) {
const name = key.substring(4).replace("_", ".");
- if (name !== Doc.CurrentUserEmail && name !== this.selectedDoc!.author/* && sidebarUsersDisplayed![name] !== false*/) {
- tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value)!));
+ const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]);
+ if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public"/* && sidebarUsersDisplayed![name] !== false*/) {
+ tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-"));
}
}
}
@@ -402,9 +432,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
// }
// })
- // shifts the current user and the owner to the top of the doc.
- tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === this.selectedDoc!.author ? "Owner" : StrCast(this.selectedDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`])));
- if (Doc.CurrentUserEmail !== this.selectedDoc!.author) tableEntries.unshift(this.sharingItem(StrCast(this.selectedDoc!.author), effectiveAcl, "Owner"));
+ // shifts the current user, owner, public to the top of the doc.
+ tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.every(doc => doc["ACL-Public"] === docs[0]["ACL-Public"]) ? (AclMap.get(target[AclSym]?.["ACL-Public"]) || SharingPermissions.None) : "-multiple-"));
+ tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-"));
+ if (Doc.CurrentUserEmail !== target.author && docs.every(doc => doc.author === docs[0].author)) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner"));
return <div className="propertiesView-sharingTable">
{tableEntries}
@@ -425,21 +456,27 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get editableTitle() {
+ const titles = new Set<string>();
+ SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
+ const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title);
return <div className="editable-title"><EditableView
key="editableView"
- contents={StrCast(this.selectedDoc?.title)}
+ contents={title}
height={25}
fontSize={14}
- GetValue={() => StrCast(this.selectedDoc?.title)}
+ GetValue={() => title}
SetValue={this.setTitle} /> </div>;
}
@undoBatch
@action
setTitle = (value: string) => {
- if (this.dataDoc) {
- this.selectedDoc && (this.selectedDoc.title = value);
- KeyValueBox.SetField(this.dataDoc, "title", value, true);
+ if (SelectionManager.SelectedDocuments().length > 1) {
+ SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true));
+ return true;
+ } else if (this.dataDoc) {
+ if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true);
+ else KeyValueBox.SetField(this.dataDoc, "title", value, true);
return true;
}
return false;
@@ -496,15 +533,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed
get controlPointsButton() {
- return <div className="inking-button">
+ const formatInstance = InkStrokeProperties.Instance;
+ return !formatInstance ? (null) : <div className="inking-button">
<Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
- <div className="inking-button-points" onPointerDown={action(() => FormatShapePane.Instance._controlBtn = !FormatShapePane.Instance._controlBtn)} style={{ backgroundColor: FormatShapePane.Instance._controlBtn ? "black" : "" }}>
+ <div className="inking-button-points" onPointerDown={action(() => formatInstance._controlBtn = !formatInstance._controlBtn)} style={{ backgroundColor: formatInstance._controlBtn ? "black" : "" }}>
<FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{FormatShapePane.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
- <div className="inking-button-lock" onPointerDown={action(() => FormatShapePane.Instance._lock = !FormatShapePane.Instance._lock)} >
- <FontAwesomeIcon icon={FormatShapePane.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
+ <Tooltip title={<div className="dash-tooltip">{formatInstance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
+ <div className="inking-button-lock" onPointerDown={action(() => formatInstance._lock = !formatInstance._lock)} >
+ <FontAwesomeIcon icon={formatInstance._lock ? "lock" : "unlock"} color="white" size="lg" />
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{"Rotate 90Ëš"}</div>}>
@@ -524,7 +562,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="inputBox-title"> {title} </div>
<input className="inputBox-input"
type="text" value={value}
- onChange={e => setter(e.target.value)} />
+ onChange={e => {
+ setter(e.target.value);
+ }}
+ onKeyPress={e => {
+ e.stopPropagation();
+ }} />
<div className="inputBox-button">
<div className="inputBox-button-up" key="up2"
onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
@@ -559,7 +602,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const oldX = NumCast(this.selectedDoc?.x);
const oldY = NumCast(this.selectedDoc?.y);
this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
- FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
+ InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
const doc = this.selectedDoc;
if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
const ink = Cast(doc.data, InkField)?.inkData;
@@ -581,7 +624,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const oX = NumCast(this.selectedDoc?.x);
const oY = NumCast(this.selectedDoc?.y);
this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
- FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
+ InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
const docu = this.selectedDoc;
if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
const ink = Cast(docu.data, InkField)?.inkData;
@@ -619,12 +662,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
set shapeWid(value) {
const oldWidth = NumCast(this.selectedDoc?._width);
this.selectedDoc && (this.selectedDoc._width = Number(value));
- FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
+ InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
}
set shapeHgt(value) {
const oldHeight = NumCast(this.selectedDoc?._height);
this.selectedDoc && (this.selectedDoc._height = Number(value));
- FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
+ InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
}
@computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
@@ -829,35 +872,33 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
if (this.selectedDoc && !this.isPres) {
return <div className="propertiesView" style={{
width: this.props.width,
+ minWidth: this.props.width
//overflowY: this.scrolling ? "scroll" : "visible"
}} >
<div className="propertiesView-title" style={{ width: this.props.width }}>
Properties
- {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}>
- <FontAwesomeIcon icon="times" color="black" size="sm" />
- </div> */}
</div>
<div className="propertiesView-name">
{this.editableTitle}
</div>
- <div className="propertiesView-settings" onPointerEnter={() => runInAction(() => { this.inActions = true; })}
- onPointerLeave={action(() => this.inActions = false)}>
+ <div className="propertiesView-settings" onPointerEnter={action(() => this.inOptions = true)}
+ onPointerLeave={action(() => this.inOptions = false)}>
<div className="propertiesView-settings-title"
- onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })}
- style={{ backgroundColor: this.openActions ? "black" : "" }}>
- Actions
+ onPointerDown={action(() => this.openOptions = !this.openOptions)}
+ style={{ backgroundColor: this.openOptions ? "black" : "" }}>
+ Options
<div className="propertiesView-settings-title-icon">
- <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ <FontAwesomeIcon icon={this.openOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
</div>
</div>
- {!this.openActions ? (null) :
+ {!this.openOptions ? (null) :
<div className="propertiesView-settings-content">
<PropertiesButtons />
</div>}
</div>
<div className="propertiesView-sharing">
<div className="propertiesView-sharing-title"
- onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })}
+ onPointerDown={action(() => this.openSharing = !this.openSharing)}
style={{ backgroundColor: this.openSharing ? "black" : "" }}>
Sharing {"&"} Permissions
<div className="propertiesView-sharing-title-icon">
@@ -903,7 +944,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{!this.isInk ? (null) :
<div className="propertiesView-appearance">
<div className="propertiesView-appearance-title"
- onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })}
+ onPointerDown={action(() => this.openAppearance = !this.openAppearance)}
style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
Appearance
<div className="propertiesView-appearance-title-icon">
@@ -918,7 +959,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{this.isInk ? <div className="propertiesView-transform">
<div className="propertiesView-transform-title"
- onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })}
+ onPointerDown={action(() => this.openTransform = !this.openTransform)}
style={{ backgroundColor: this.openTransform ? "black" : "" }}>
Transform
<div className="propertiesView-transform-title-icon">
@@ -932,7 +973,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="propertiesView-fields">
<div className="propertiesView-fields-title"
- onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })}
+ onPointerDown={action(() => this.openFields = !this.openFields)}
style={{ backgroundColor: this.openFields ? "black" : "" }}>
Fields {"&"} Tags
<div className="propertiesView-fields-title-icon">
@@ -948,12 +989,23 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{novice ? this.noviceFields : this.expandedField}
</div>}
</div>
+ <div className="propertiesView-contexts">
+ <div className="propertiesView-contexts-title"
+ onPointerDown={action(() => this.openContexts = !this.openContexts)}
+ style={{ backgroundColor: this.openContexts ? "black" : "" }}>
+ Contexts
+ <div className="propertiesView-contexts-title-icon">
+ <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openContexts ? <div className="propertiesView-contexts-content" >{this.contexts}</div> : null}
+ </div>
<div className="propertiesView-layout">
<div className="propertiesView-layout-title"
- onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}
+ onPointerDown={action(() => this.openLayout = !this.openLayout)}
style={{ backgroundColor: this.openLayout ? "black" : "" }}>
Layout
- <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}>
+ <div className="propertiesView-layout-title-icon">
<FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
</div>
</div>
@@ -991,7 +1043,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>}
{!selectedItem ? (null) : <div className="propertiesView-presTrails">
<div className="propertiesView-presTrails-title"
- onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })}
+ onPointerDown={action(() => { this.openPresProgressivize = !this.openPresProgressivize; })}
style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}>
&nbsp; <FontAwesomeIcon icon={"tasks"} /> &nbsp; Progressivize
<div className="propertiesView-presTrails-title-icon">
@@ -1004,7 +1056,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>}
{!selectedItem ? (null) : <div className="propertiesView-presTrails">
<div className="propertiesView-presTrails-title"
- onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })}
+ onPointerDown={action(() => { this.openSlideOptions = !this.openSlideOptions; })}
style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
&nbsp; <FontAwesomeIcon icon={"cog"} /> &nbsp; {PresBox.Instance.stringType} options
<div className="propertiesView-presTrails-title-icon">
@@ -1017,7 +1069,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>}
<div className="propertiesView-presTrails">
<div className="propertiesView-presTrails-title"
- onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })}
+ onPointerDown={action(() => { this.openAddSlide = !this.openAddSlide; })}
style={{ backgroundColor: this.openAddSlide ? "black" : "" }}>
&nbsp; <FontAwesomeIcon icon={"plus"} /> &nbsp; Add new slide
<div className="propertiesView-presTrails-title-icon">
@@ -1030,7 +1082,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
{/* <div className="propertiesView-sharing">
<div className="propertiesView-sharing-title"
- onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })}
+ onPointerDown={acition(() => { this.openSharing = !this.openSharing; })}
style={{ backgroundColor: this.openSharing ? "black" : "" }}>
Sharing {"&"} Permissions
<div className="propertiesView-sharing-title-icon">
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index db087fb23..c598b2861 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,17 +1,12 @@
-import * as React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import './ScriptingRepl.scss';
-import { Scripting, CompileScript, ts, Transformer } from '../util/Scripting';
+import * as React from 'react';
import { DocumentManager } from '../util/DocumentManager';
-import { OverlayView } from './OverlayView';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
+import { CompileScript, Scripting, Transformer, ts } from '../util/Scripting';
import { DocumentIconContainer } from './nodes/DocumentIcon';
-
-library.add(faCaretDown);
-library.add(faCaretRight);
+import { OverlayView } from './OverlayView';
+import './ScriptingRepl.scss';
@observer
export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void, value: { [key: string]: any }, name?: string }> {
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index eb20fc257..870af03aa 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast } from "../../fields/Doc";
import { Docs, DocUtils, } from "../documents/Documents";
import { StrCast, Cast } from "../../fields/Types";
import { CollectionTreeView } from "./collections/CollectionTreeView";
-import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero, returnEmptyFilter } from "../../Utils";
+import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero, returnEmptyFilter, returnEmptyDoclist } from "../../Utils";
import { Transform } from "../util/Transform";
import { ScriptField, ComputedField } from "../../fields/ScriptField";
import { Scripting } from "../util/Scripting";
@@ -116,9 +116,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
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) =>
- templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
- templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
+ //this.props.templates.forEach((checked, template) =>
+ // templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
+ //templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
@@ -133,6 +133,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
rootSelected={returnFalse}
onCheckedClick={this.scriptField}
onChildClick={this.scriptField}
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 1b81c544a..e84022366 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -331,7 +331,7 @@ export class Keyframe extends React.Component<IProps> {
}),
TimelineMenu.Instance.addItem("button", "Show Data", action(() => {
const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
- CollectionDockingView.AddRightSplit(kvp, emptyPath);
+ CollectionDockingView.AddSplit(kvp, "right");
})),
TimelineMenu.Instance.addItem("button", "Delete", action(() => {
(this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index df828897e..1131ec874 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -83,14 +83,11 @@ export class Timeline extends React.Component<FieldViewProps> {
}
/////////lifecycle functions////////////
- componentWillMount() {
+ componentDidMount() {
const relativeHeight = window.innerHeight / 20; //sets height to arbitrary size, relative to innerHeight
this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
- }
-
- componentDidMount() {
runInAction(() => {
if (!this.props.Document.AnimationLength) { //if animation length did not exist
this.props.Document.AnimationLength = this._time; //set it to default time
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 6ebd5103b..96f5afcd9 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,24 +1,5 @@
@import "../../views/globalCssVariables.scss";
-.miniMap {
- position: absolute;
- overflow: hidden;
- right: 10;
- bottom: 10;
- border: solid 1px;
- box-shadow: black 0.4vw 0.4vw 0.8vw;
-
- .miniOverlay {
- width: 100%;
- height: 100%;
- position: absolute;
-
- .miniThumb {
- background: #25252525;
- position: absolute;
- }
- }
-}
.lm_title {
margin-top: 3px;
@@ -69,19 +50,7 @@
}
.lm_popout {
- display: none;
-}
-
-.messageCounter {
- width: 18px;
- height: 20px;
- text-align: center;
- border-radius: 20px;
- margin-left: 5px;
- transform: translate(0px, -8px);
- display: inline-block;
- background: transparent;
- border: 1px #999999 solid;
+ display: inline;
}
.collectiondockingview-container {
@@ -101,7 +70,7 @@
margin: auto;
}
- .collectionDockingView-dragAsDocument {
+ .collectionDockingView-drag {
touch-action: none;
position: absolute;
padding-left: 5px;
@@ -119,6 +88,10 @@
transform: scale(1.2);
}
+ .lm_controls .lm_popout {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAAAAABHICnvAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfkCBsXMgbrEyzaAAAAT0lEQVQY02NgIAcIu8tgEW3/u4IDQ5B14/8LQlhFhckVFfCJjIyIOfP/QWpEZGSQJFS05s9fIPj3/z+YmseCTxS7CZS7DI+PsYcOjpAkDAA6H0KZxzDzlgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wOC0yN1QyMzo1MDowNi0wNDowMDvgVpQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDgtMjdUMjM6NTA6MDYtMDQ6MDBKve4oAAAAAElFTkSuQmCC)
+ }
+
.lm_maximised .lm_controls .lm_maximise {
opacity: 1;
transform: scale(0.8);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 43da0d3cf..c891d2035 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,46 +1,32 @@
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, computed, Lambda, observable, reaction, runInAction, trace, IReactionDisposer } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import * as GoldenLayout from "../../../client/goldenLayout";
-import { DateField } from '../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { Id } from '../../../fields/FieldSymbols';
-import { FieldId } from "../../../fields/RefField";
+import { InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse, emptyPath, aggregateBounds } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from "../../util/DragManager";
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { DragManager } from "../../util/DragManager";
+import { InteractionUtils } from '../../util/InteractionUtils';
import { Scripting } from '../../util/Scripting';
-import { SelectionManager } from '../../util/SelectionManager';
-import { Transform } from '../../util/Transform';
-import { undoBatch } from "../../util/UndoManager";
-import { MainView } from '../MainView';
-import { DocumentView } from "../nodes/DocumentView";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import "./CollectionDockingView.scss";
-import { SubCollectionViewProps } from "./CollectionSubView";
-import { DockingViewButtonSelector } from './ParentDocumentSelector';
-import React = require("react");
+import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
import { CollectionViewType } from './CollectionView';
-import { SnappingManager } from '../../util/SnappingManager';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { listSpec } from '../../../fields/Schema';
-import { clamp } from 'lodash';
-import { PresBox } from '../nodes/PresBox';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { InteractionUtils } from '../../util/InteractionUtils';
-import { InkTool } from '../../../fields/InkField';
+import { TabDocView } from './TabDocView';
+import React = require("react");
const _global = (window /* browser */ || global /* node */) as any;
@observer
-export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
- @observable public static Instances: CollectionDockingView[] = [];
- @computed public static get Instance() { return CollectionDockingView.Instances[0]; }
- public static makeDocumentConfig(document: Doc, width?: number, libraryPath?: Doc[]) {
+export class CollectionDockingView extends CollectionSubView(doc => doc) {
+ @observable public static Instance: CollectionDockingView;
+ public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
return {
type: 'react-component',
component: 'DocumentFrameRenderer',
@@ -48,341 +34,212 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
width: width,
props: {
documentId: document[Id],
- libraryPath: libraryPath?.map(d => d[Id])
+ panelName // name of tab that can be used to close or replace its contents
}
};
}
- @computed public get initialized() {
- return this._goldenLayout !== null;
- }
-
- @observable private _goldenLayout: any = null;
+ private _reactionDisposer?: IReactionDisposer;
private _containerRef = React.createRef<HTMLDivElement>();
- private _flush: boolean = false;
+ private _flush: UndoManager.Batch | undefined;
private _ignoreStateChange = "";
- private _isPointerDown = false;
- private _maximizedSrc: Opt<DocumentView>;
+ public tabMap: Set<any> = new Set();
+ public get initialized() { return this._goldenLayout !== null; }
+ public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; }
+ @observable private _goldenLayout: any = null;
constructor(props: SubCollectionViewProps) {
super(props);
- runInAction(() => !CollectionDockingView.Instances ? CollectionDockingView.Instances = [this] : CollectionDockingView.Instances.push(this));
+ runInAction(() => CollectionDockingView.Instance = this);
//Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
DragManager.StartWindowDrag = this.StartOtherDrag;
}
+
public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
- let config: any;
- if (dragDocs.length === 1) {
- config = CollectionDockingView.makeDocumentConfig(dragDocs[0]);
- } else {
- config = {
- type: 'row',
- content: dragDocs.map((doc, i) => {
- CollectionDockingView.makeDocumentConfig(doc);
- })
- };
- }
- const div = document.createElement("div");
- const dragSource = this._goldenLayout.createDragSource(div, config);
- dragSource._dragListener.on("dragStop", () => {
- dragSource.destroy();
- });
+ !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
+ const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
+ { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) };
+ const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
+ //dragSource._dragListener.on("dragStop", dragSource.destroy);
dragSource._dragListener.onMouseDown(e);
}
@undoBatch
- @action
- public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) {
- if (docView.props.Document._viewType === CollectionViewType.Docking && docView.props.Document.layoutKey === "layout") {
- return MainView.Instance.openWorkspace(docView.props.Document);
- }
- const document = Doc.MakeAlias(docView.props.Document);
- const newItemStackConfig = {
- type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
- };
- const docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
- this._goldenLayout.root.contentItems[0].addChild(docconfig);
- docconfig.callDownwards('_$init');
- this._goldenLayout._$maximiseItem(docconfig);
- this._maximizedSrc = docView;
- this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+ public CloseFullScreen = () => {
+ this._goldenLayout._maximisedItem?.toggleMaximise();
this.stateChanged();
- SelectionManager.DeselectAll();
}
- public CloseFullScreen = () => {
- const target = this._goldenLayout._maximisedItem;
- if (target !== null && this._maximizedSrc) {
- this._goldenLayout._maximisedItem.remove();
- SelectionManager.SelectDoc(this._maximizedSrc, false);
- this._maximizedSrc = undefined;
- this.stateChanged();
+ @undoBatch
+ public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
+ const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document);
+ if (tab) {
+ const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+ if (j !== -1) {
+ tab.header.parent.contentItems[j].remove();
+ return CollectionDockingView.Instance.layoutChanged();
+ }
}
- }
- public HasFullScreen = () => {
- return this._goldenLayout._maximisedItem !== null;
+ return false;
}
@undoBatch
- @action
- public static CloseRightSplit(document: Opt<Doc>): boolean {
+ public static OpenFullScreen(doc: Doc, libraryPath?: Doc[]) {
const instance = CollectionDockingView.Instance;
- const tryClose = (childItem: any) => {
- if (childItem.config?.component === "DocumentFrameRenderer") {
- const docView = DocumentManager.Instance.getDocumentViewById(childItem.config.props.documentId);
- if (docView && ((!document && docView.Document.isDisplayPanel) || (document && Doc.AreProtosEqual(docView.props.Document, document)))) {
- childItem.remove();
- instance.layoutChanged(document);
- return true;
- }
- }
- return false;
+ if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
+ return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+ }
+ const newItemStackConfig = {
+ type: 'stack',
+ content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))]
};
- const retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false :
- Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
-
- retVal && instance.stateChanged();
- return retVal;
+ const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+ instance._goldenLayout.root.contentItems[0].addChild(docconfig);
+ docconfig.callDownwards('_$init');
+ instance._goldenLayout._$maximiseItem(docconfig);
+ instance._goldenLayout.emit('stateChanged');
+ instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
+ instance.stateChanged();
+ return true;
}
- @action
- layoutChanged(removed?: Doc) {
- this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
- this._goldenLayout.emit('stateChanged');
- this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
- if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
- this.stateChanged();
- }
@undoBatch
- @action
- public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean {
- if (!CollectionDockingView.Instance) return false;
+ public static ReplaceTab(document: Doc, panelName: string, stack: any, addToSplit?: boolean): boolean {
const instance = CollectionDockingView.Instance;
- let retVal = false;
- if (instance._goldenLayout.root.contentItems[0].isRow) {
- retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
- if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
- DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) {
- const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
- child.addChild(newItemStackConfig, undefined);
- !addToSplit && child.contentItems[0].remove();
- instance.layoutChanged(document);
- return true;
- }
- return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
- if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.Document.isDisplayPanel) {
- const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
- child.addChild(newItemStackConfig, undefined);
- !addToSplit && child.contentItems[j].remove();
- instance.layoutChanged(document);
- return true;
- }
- return false;
- });
- });
+ if (!instance) return false;
+ const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+ if (!panelName && stack) {
+ const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
+ const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
+ stack.addChild(newContentItem.contentItems[0], undefined);
+ stack.contentItems[activeContentItemIndex].remove();
+ return CollectionDockingView.Instance.layoutChanged();
}
- if (retVal) {
- instance.stateChanged();
+ const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => tab.contentItem.config.props.panelName === panelName);
+ if (tab) {
+ tab.header.parent.addChild(newConfig, undefined);
+ const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+ !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
+ return CollectionDockingView.Instance.layoutChanged();
}
- return retVal;
+ return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
}
- //
- // Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
- //
- @undoBatch
- @action
- public static AddRightSplit(document: Doc, libraryPath?: Doc[]) {
- if (!CollectionDockingView.Instance) return false;
- const instance = CollectionDockingView.Instance;
- const newItemStackConfig = {
- type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
- };
-
- const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
- if (instance._goldenLayout.root.contentItems.length === 0) {
- instance._goldenLayout.root.addChild(newContentItem);
- } else if (instance._goldenLayout.root.contentItems[0].isRow) {
- instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
- } else {
- const collayout = instance._goldenLayout.root.contentItems[0];
- const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
- collayout.parent.replaceChild(collayout, newRow);
- newRow.addChild(newContentItem, undefined, true);
- newRow.addChild(collayout, 0, true);
-
- collayout.config.width = 50;
- newContentItem.config.width = 50;
- }
- newContentItem.callDownwards('_$init');
- instance.layoutChanged();
- return true;
+ @undoBatch
+ public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
+ return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
+ CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName);
}
-
//
// Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
//
@undoBatch
- @action
- public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) {
- if (!CollectionDockingView.Instance) return false;
+ public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
+ if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
const instance = CollectionDockingView.Instance;
- const newItemStackConfig = {
- type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
- };
+ if (!instance) return false;
+ const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
- const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-
- if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
- instance._goldenLayout.root.addChild(newContentItem);
- } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
- if (pullSide === "left") {
- instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
- } else if (pullSide === "right") {
- instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
- } else if (pullSide === "top" || pullSide === "bottom") {
- // if not going in a row layout, must add already existing content into column
- const rowlayout = instance._goldenLayout.root.contentItems[0];
- const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
- rowlayout.parent.replaceChild(rowlayout, newColumn);
- if (pullSide === "top") {
- newColumn.addChild(rowlayout, undefined, true);
- newColumn.addChild(newContentItem, 0, true);
- } else if (pullSide === "bottom") {
- newColumn.addChild(newContentItem, undefined, true);
- newColumn.addChild(rowlayout, 0, true);
- }
+ if (!pullSide && stack) {
+ stack.addChild(docContentConfig, undefined);
+ } else {
+ const newItemStackConfig = { type: 'stack', content: [docContentConfig] };
+ const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+ if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
+ instance._goldenLayout.root.addChild(newContentItem);
+ } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
+ switch (pullSide) {
+ default:
+ case "right": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+ case "left": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+ case "top":
+ case "bottom":
+ // if not going in a row layout, must add already existing content into column
+ const rowlayout = instance._goldenLayout.root.contentItems[0];
+ const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
+ rowlayout.parent.replaceChild(rowlayout, newColumn);
+ if (pullSide === "top") {
+ newColumn.addChild(rowlayout, undefined, true);
+ newColumn.addChild(newContentItem, 0, true);
+ } else if (pullSide === "bottom") {
+ newColumn.addChild(newContentItem, undefined, true);
+ newColumn.addChild(rowlayout, 0, true);
+ }
- rowlayout.config.height = 50;
- newContentItem.config.height = 50;
- }
- } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
- if (pullSide === "top") {
- instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
- } else if (pullSide === "bottom") {
- instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
- } else if (pullSide === "left" || pullSide === "right") {
- // if not going in a row layout, must add already existing content into column
- const collayout = instance._goldenLayout.root.contentItems[0];
- const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
- collayout.parent.replaceChild(collayout, newRow);
-
- if (pullSide === "left") {
- newRow.addChild(collayout, undefined, true);
- newRow.addChild(newContentItem, 0, true);
- } else if (pullSide === "right") {
- newRow.addChild(newContentItem, undefined, true);
- newRow.addChild(collayout, 0, true);
+ rowlayout.config.height = 50;
+ newContentItem.config.height = 50;
}
+ } else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+ switch (pullSide) {
+ case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+ case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+ case "left":
+ case "right":
+ default:
+ // if not going in a row layout, must add already existing content into column
+ const collayout = instance._goldenLayout.root.contentItems[0];
+ const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+ collayout.parent.replaceChild(collayout, newRow);
+
+ if (pullSide === "left") {
+ newRow.addChild(collayout, undefined, true);
+ newRow.addChild(newContentItem, 0, true);
+ } else {
+ newRow.addChild(newContentItem, undefined, true);
+ newRow.addChild(collayout, 0, true);
+ }
- collayout.config.width = 50;
- newContentItem.config.width = 50;
- }
- }
-
- newContentItem.callDownwards('_$init');
- instance.layoutChanged();
- return true;
- }
-
-
- //
- // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
- //
- @undoBatch
- @action
- public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) {
- document.isDisplayPanel = true;
- if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) {
- CollectionDockingView.AddRightSplit(document, libraryPath);
- }
- }
-
- @undoBatch
- @action
- public AddTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
- Doc.GetProto(document).lastOpened = new DateField;
- const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
- if (stack === undefined) {
- let stack: any = this._goldenLayout.root;
- while (!stack.isStack) {
- if (stack.contentItems.length) {
- stack = stack.contentItems[0];
- } else {
- stack.addChild({ type: 'stack', content: [docContentConfig] });
- stack = undefined;
- break;
+ collayout.config.width = 50;
+ newContentItem.config.width = 50;
}
}
- if (stack) {
- stack.addChild(docContentConfig);
- }
- } else {
- stack.addChild(docContentConfig, undefined);
+ instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
+ newContentItem.callDownwards('_$init');
}
- this.layoutChanged();
- return true;
+
+ return instance.layoutChanged();
}
@undoBatch
@action
- public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => {
- Doc.GetProto(document).lastOpened = new DateField;
- const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath);
- if (stack === undefined) {
- let stack: any = this._goldenLayout.root;
- while (!stack.isStack) {
- if (stack.contentItems.length) {
- stack = stack.contentItems[0];
- } else {
- stack.addChild({ type: 'stack', content: [docContentConfig] });
- stack = undefined;
- break;
- }
- }
- if (stack) {
- stack.addChild(docContentConfig);
- }
- } else {
- stack.addChild(docContentConfig, undefined);
- }
- this.layoutChanged();
+ layoutChanged() {
+ this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+ this._goldenLayout.emit('stateChanged');
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+ this.stateChanged();
return true;
}
- setupGoldenLayout() {
+ async setupGoldenLayout() {
const config = StrCast(this.props.Document.dockingConfig);
if (config) {
- if (!this._goldenLayout) {
- runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
- }
- else {
+ const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) ?? [];
+ await Promise.all(docids.map(id => DocServer.GetRefField(id)));
+
+ if (this._goldenLayout) {
if (config === JSON.stringify(this._goldenLayout.toConfig())) {
return;
+ } else {
+ try {
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) { }
}
- try {
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
- } catch (e) { }
- this._goldenLayout.destroy();
- runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
}
- this._goldenLayout.on('itemDropped', this.itemDropped);
+ this.tabMap.clear();
+ this._goldenLayout?.destroy();
+ runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
this._goldenLayout.on('tabCreated', this.tabCreated);
this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
this._goldenLayout.on('stackCreated', this.stackCreated);
- this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
+ this._goldenLayout.registerComponent('DocumentFrameRenderer', TabDocView);
this._goldenLayout.container = this._containerRef.current;
if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
try {
@@ -394,582 +251,152 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this._goldenLayout.init();
}
}
- reactionDisposer?: Lambda;
+
componentDidMount: () => void = () => {
if (this._containerRef.current) {
- const observer = new _global.ResizeObserver(action((entries: any) => {
- for (const entry of entries) {
- this.onResize(null as any);
- }
- }));
- observer.observe(this._containerRef.current);
- this.reactionDisposer = reaction(
- () => this.props.Document.dockingConfig,
- () => {
- if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
- // Because this is in a set timeout, if this component unmounts right after mounting,
- // we will leak a GoldenLayout, because we try to destroy it before we ever create it
- setTimeout(() => this.setupGoldenLayout(), 1);
- DocListCast((Doc.UserDoc().myWorkspaces as Doc).data).map(d => d.workspaceBrush = false);
- this.props.Document.workspaceBrush = true;
+ new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
+ this._reactionDisposer = reaction(() => StrCast(this.props.Document.dockingConfig),
+ config => {
+ if (!this._goldenLayout || this._ignoreStateChange !== config) { // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
+ this.setupGoldenLayout();
}
this._ignoreStateChange = "";
- }, { fireImmediately: true });
-
- window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
+ });
+ setTimeout(() => this.setupGoldenLayout(), 0);
+ //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
}
}
+
componentWillUnmount: () => void = () => {
try {
- this.props.Document.workspaceBrush = false;
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
this._goldenLayout.unbind('stackCreated', this.stackCreated);
this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- } catch (e) {
-
- }
- this._goldenLayout && this._goldenLayout.destroy();
- runInAction(() => {
- CollectionDockingView.Instances.splice(CollectionDockingView.Instances.indexOf(this), 1);
- this._goldenLayout = null;
- });
+ } catch (e) { }
+ this._goldenLayout?.destroy();
window.removeEventListener('resize', this.onResize);
- this.reactionDisposer && this.reactionDisposer();
+ this._reactionDisposer?.();
}
+
@action
onResize = (event: any) => {
const cur = this._containerRef.current;
-
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
- this._goldenLayout?.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+ cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
}
@action
- onPointerUp = (e: React.PointerEvent): void => {
+ onPointerUp = (e: MouseEvent): void => {
+ window.removeEventListener("pointerup", this.onPointerUp);
if (this._flush) {
- this._flush = false;
setTimeout(() => {
CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
this.stateChanged();
+ this._flush!.end();
+ this._flush = undefined;
}, 10);
}
}
+
@action
onPointerDown = (e: React.PointerEvent): void => {
- this._isPointerDown = true;
- const onPointerUp = action(() => {
- window.removeEventListener("pointerup", onPointerUp);
- this._isPointerDown = false;
- });
- window.addEventListener("pointerup", onPointerUp);
- const className = (e.target as any).className;
- if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
- this._flush = true;
+ window.addEventListener("mouseup", this.onPointerUp);
+ if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
+ this._flush = UndoManager.StartBatch("golden layout edit");
}
- if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
- return;
- } else {
+ if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
+ Doc.GetSelectedTool() !== InkTool.Highlighter && Doc.GetSelectedTool() !== InkTool.Pen) {
e.stopPropagation();
}
}
- updateDataField = async (json: string) => {
+ public static Copy(doc: Doc) {
+ let json = StrCast(doc.dockingConfig);
const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
- const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
+ const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || [];
+ const docs = docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
+ const newtabs = docs.map(doc => {
+ const copy = Doc.MakeAlias(doc);
+ json = json.replace(doc[Id], copy[Id]);
+ return copy;
+ });
+ const copy = Docs.Create.DockDocument(newtabs, json, { title: "Snapshot: " + doc.title });
+ const docsublists = DocListCast(doc.data);
+ const copysublists = DocListCast(copy.data);
+ const docother = Cast(docsublists[1], Doc, null);
+ const copyother = Cast(copysublists[1], Doc, null);
+ const newother = DocListCast(docother.data).map(doc => Doc.MakeAlias(doc));
+ Doc.GetProto(copyother).data = new List<Doc>(newother);
- if (docids) {
- const docs = (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
- docs.map(doc => Doc.AddDocToList(Doc.GetProto(this.props.Document), this.props.fieldKey, doc));
- // Doc.GetProto(this.props.Document)[this.props.fieldKey] = new List<Doc>(docs);
- }
+ return copy;
}
- @undoBatch
+ @action
stateChanged = () => {
const json = JSON.stringify(this._goldenLayout.toConfig());
- this.props.Document.dockingConfig = json;
- this.updateDataField(json);
- }
-
- itemDropped = () => {
- CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
- this.stateChanged();
- }
-
- htmlToElement(html: string) {
- const template = document.createElement('template');
- html = html.trim(); // Never return a text node of whitespace as the result
- template.innerHTML = html;
- return template.content.firstChild;
- }
-
- tabCreated = async (tab: any) => {
- tab.titleElement[0].Tab = tab;
- if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
- if (tab.contentItem.config.fixed) {
- tab.contentItem.parent.config.fixed = true;
- }
+ const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
+ const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
- const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
- if (doc instanceof Doc) {
- tab.titleElement[0].onclick = (e: any) => {
- if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
- tab.titleElement[0].lastClick = Date.now();
- tab.titleElement[0].focus();
- };
- tab.titleElement[0].onchange = (e: any) => {
- tab.titleElement[0].size = e.currentTarget.value.length + 1;
- Doc.GetProto(doc).title = e.currentTarget.value, true;
- };
- tab.titleElement[0].size = StrCast(doc.title).length + 1;
- tab.titleElement[0].value = doc.title;
- tab.titleElement[0].style["max-width"] = "100px";
- const gearSpan = document.createElement("span");
- gearSpan.className = "collectionDockingView-gear";
- gearSpan.style.position = "relative";
- gearSpan.style.paddingLeft = "0px";
- gearSpan.style.paddingRight = "12px";
- const stack = tab.contentItem.parent;
- tab.element[0].onpointerdown = (e: any) => {
- const view = DocumentManager.Instance.getDocumentView(doc);
- view && SelectionManager.SelectDoc(view, false);
- };
- // shifts the focus to this tab when another tab is dragged over it
- tab.element[0].onmouseenter = (e: any) => {
- if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return;
- const activeContentItem = tab.header.parent.getActiveContentItem();
- if (tab.contentItem !== activeContentItem) {
- tab.header.parent.setActiveContentItem(tab.contentItem);
- }
- tab.setActive(true);
- };
- const onDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e) => {
- if (!(e as any).defaultPrevented) {
- const dragData = new DragManager.DocumentDragData([doc]);
- dragData.dropAction = doc.dropAction as dropActionType;
- DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
- return true;
- }
- return false;
- }, returnFalse, emptyFunction);
- };
-
- tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
- (views) => {
- 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);
- tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
- tab.titleElement[0].textContent = title, { fireImmediately: true };
- tab.titleElement[0].style.padding = degree ? 0 : 2;
- tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
- });
- //TODO why can't this just be doc instead of the id?
- tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
- }
- }
- tab.closeElement.off('click') //unbind the current click handler
- .click(async function () {
- tab.reactionDisposer?.();
- tab.buttonDisposer?.();
- const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
- if (doc instanceof Doc) {
- const theDoc = doc;
- CollectionDockingView.Instance._removedDocs.push(theDoc);
-
- const recent = await Cast(Doc.UserDoc().myRecentlyClosed, Doc);
- if (recent) {
- Doc.AddDocToList(recent, "data", doc, undefined, true, true);
- }
- SelectionManager.DeselectAll();
- }
- CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
- tab.contentItem.remove();
- CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
- });
+ this.props.Document.dockingConfig = json;
+ const sublists = DocListCast(this.props.Document[this.props.fieldKey]);
+ const tabs = Cast(sublists[0], Doc, null);
+ const other = Cast(sublists[1], Doc, null);
+ const tabdocs = DocListCast(tabs.data);
+ const otherdocs = DocListCast(other.data);
+ Doc.GetProto(tabs).data = new List<Doc>(docs);
+ const otherSet = new Set<Doc>();
+ otherdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+ tabdocs.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+ Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
}
tabDestroyed = (tab: any) => {
- if (tab.reactComponents) {
- for (const ele of tab.reactComponents) {
- ReactDOM.unmountComponentAtNode(ele);
- }
- }
+ this.tabMap.delete(tab);
+ Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+ tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+ }
+ tabCreated = (tab: any) => {
+ tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
}
- _removedDocs: Doc[] = [];
stackCreated = (stack: any) => {
- //stack.header.controlsContainer.find('.lm_popout').hide();
- stack.header.element.on('mousedown', (e: any) => {
- if (e.target === stack.header.element[0] && e.button === 1) {
- this.AddTab(stack, Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: "Untitled Collection" }));
+ stack.header?.element.on('mousedown', (e: any) => {
+ if (e.target === stack.header?.element[0] && e.button === 2) {
+ const emptyPane = CurrentUserUtils.EmptyPane;
+ emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
+ }), "", stack);
}
});
- // starter code for bezel to add new pane
- // stack.element.on("touchstart", (e: TouchEvent) => {
- // if (e.targetTouches.length === 2) {
- // let pt1 = e.targetTouches.item(0);
- // let pt2 = e.targetTouches.item(1);
- // let threshold = 40 * window.devicePixelRatio;
- // if (pt1 && pt2 && InteractionUtils.TwoPointEuclidist(pt1, pt2) < threshold) {
- // let edgeThreshold = 30 * window.devicePixelRatio;
- // let center = InteractionUtils.CenterPoint([pt1, pt2]);
- // let stackRect: DOMRect = stack.element.getBoundingClientRect();
- // let nearLeft = center.X - stackRect.x < edgeThreshold;
- // let nearTop = center.Y - stackRect.y < edgeThreshold;
- // let nearRight = stackRect.right - center.X < edgeThreshold;
- // let nearBottom = stackRect.bottom - center.Y < edgeThreshold;
- // let ns = [nearLeft, nearTop, nearRight, nearBottom].filter(n => n);
- // if (ns.length === 1) {
-
- // }
- // }
- // }
- // });
- stack.header.controlsContainer.find('.lm_close') //get the close icon
+ stack.header?.controlsContainer.find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
- .click(action(async function () {
+ .click(action(() => {
//if (confirm('really close this?')) {
-
stack.remove();
- stack.contentItems.forEach(async (contentItem: any) => {
- const doc = await DocServer.GetRefField(contentItem.config.props.documentId);
- if (doc instanceof Doc) {
- let recent: Doc | undefined;
- if (recent = await Cast(Doc.UserDoc().myRecentlyClosed, Doc)) {
- Doc.AddDocToList(recent, "data", doc, undefined, true, true);
- }
- const theDoc = doc;
- CollectionDockingView.Instance._removedDocs.push(theDoc);
- }
- });
- //}
+ stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true));
}));
- stack.header.controlsContainer.find('.lm_popout') //get the close icon
+ stack.header?.controlsContainer.find('.lm_popout') //get the close icon
.off('click') //unbind the current click handler
- .click(action(function () {
- stack.config.fixed = !stack.config.fixed;
- // const url = Utils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
- // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
+ .click(action(() => {
+ // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size
+ const emptyPane = CurrentUserUtils.EmptyPane;
+ emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
+ }), "", stack);
}));
}
render() {
- if (this.props.renderDepth > 0) {
- return <div style={{ width: "100%", height: "100%" }}>Nested workspaces can't be rendered</div>;
- }
- return <div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />;
- }
-
-}
-
-interface DockedFrameProps {
- documentId: FieldId;
- glContainer: any;
- libraryPath: (FieldId[]);
- //collectionDockingView: CollectionDockingView
-}
-@observer
-export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- _mainCont: HTMLDivElement | null = null;
- @observable private _libraryPath: Doc[] = [];
- @observable private _panelWidth = 0;
- @observable private _panelHeight = 0;
- @observable private _document: Opt<Doc>;
- @observable private _isActive: boolean = false;
- _tabReaction: IReactionDisposer | undefined;
-
- get _stack(): any {
- return (this.props as any).glContainer.parent.parent;
- }
- get _tab(): any {
- const tab = (this.props as any).glContainer.tab?.element[0] as HTMLElement;
- return tab?.getElementsByClassName("lm_title")?.[0];
- }
- constructor(props: any) {
- super(props);
- DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
- this.props.libraryPath && this.setupLibraryPath();
- }
-
- async setupLibraryPath() {
- Promise.all(this.props.libraryPath.map(async docid => {
- const d = await DocServer.GetRefField(docid);
- return d instanceof Doc ? d : undefined;
- })).then(action((list: (Doc | undefined)[]) => this._libraryPath = list.filter(d => d).map(d => d as Doc)));
- }
-
- /**
- * Adds a document to the presentation view
- **/
- @undoBatch
- @action
- 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 (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
- if (!DocumentManager.Instance.getDocumentView(curPres)) {
- CollectionDockingView.AddRightSplit(curPres);
- }
- DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null));
- }
- }
- }
- /**
- * Adds a document to the presentation view
- **/
- @undoBatch
- @action
- public static UnpinDoc(doc: Doc) {
- //add this new doc to props.Document
- const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
- if (curPres) {
- const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc));
- ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]);
- }
- }
-
- componentDidMount() {
- const observer = new _global.ResizeObserver(action((entries: any) => {
- for (const entry of entries) {
- this._panelWidth = entry.contentRect.width;
- this._panelHeight = entry.contentRect.height;
- }
- }));
- observer.observe(this.props.glContainer._element[0]);
- this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
- this.props.glContainer.on("tab", this.onActiveContentItemChanged);
- this.onActiveContentItemChanged();
- this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white") }),
- (data) => {
- const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document));
- this._tab && (this._tab.style.backgroundColor = selected ? data.color : "");
- }
- );
- }
-
- componentWillUnmount() {
- this._tabReaction?.();
- this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
- this.props.glContainer.off("tab", this.onActiveContentItemChanged);
- }
-
- @action.bound
- private onActiveContentItemChanged() {
- if (this.props.glContainer.tab) {
- this._isActive = this.props.glContainer.tab.isActive;
- setTimeout(() => {
- const dv = this._document && DocumentManager.Instance.getFirstDocumentView(this._document);
- dv && SelectionManager.SelectDoc(dv, false);
- });
- !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
- }
- }
-
- get layoutDoc() { return this._document && Doc.Layout(this._document); }
- 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;
-
- contentScaling = () => {
- const nativeH = this.nativeHeight();
- const nativeW = this.nativeWidth();
- let scaling = 1;
- if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
- scaling = 1;
- } else if (NumCast(this.layoutDoc!._nativeWidth) && ((this.layoutDoc?._fitWidth) ||
- this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth))) {
- scaling = this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
- } else if (nativeW && nativeH) {
- // if (this.layoutDoc!.type === DocumentType.PDF || this.layoutDoc!.type === DocumentType.WEB) {
- // if ((this.layoutDoc?._fitWidth) ||
- // this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
- // return this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
- // } else {
- // return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight);
- // }
- // }
- const wscale = this.panelWidth() / nativeW;
- scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
- } else scaling = 1;
- return scaling;
- }
-
- ScreenToLocalTransform = () => {
- if (this._mainCont && this._mainCont.children) {
- const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
- const scale = Utils.GetScreenTransform(this._mainCont).scale;
- return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
- }
- 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; }
-
- addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
- SelectionManager.DeselectAll();
- if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
- return MainView.Instance.openWorkspace(doc);
- } else if (location === "onRight") {
- return CollectionDockingView.AddRightSplit(doc, libraryPath);
- } else if (location === "close") {
- return CollectionDockingView.CloseRightSplit(doc);
- } else if (location === "replace") {
- CollectionDockingView.UseRightSplit(doc);
- return true;
- } else {// if (location === "inPlace") {
- return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
- }
- }
-
- @computed get renderContentBounds() {
- const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
- const xbounds = bounds[2] - bounds[0];
- const ybounds = bounds[3] - bounds[1];
- const dim = Math.max(xbounds, ybounds);
- return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
- }
- @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
- @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
- @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
- @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
- childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
- returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
- miniDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
- this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
- this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
- return false;
- }), emptyFunction, emptyFunction);
- }
- getCurrentFrame = (): number => {
- const presTargetDoc = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null);
- const currentFrame = Cast(presTargetDoc.currentFrame, "number", null);
- return currentFrame;
- }
-
- renderMiniMap() {
- return <div className="miniMap" style={{
- width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
- StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!, 0))),
- }}>
- <CollectionFreeFormView
- Document={this._document!}
- LibraryPath={emptyPath}
- CollectionView={undefined}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ChildLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid havin to set stuff like this.
- noOverlay={true} // don't render overlay Docs since they won't scale
- active={returnTrue}
- select={emptyFunction}
- dropAction={undefined}
- isSelected={returnFalse}
- dontRegisterView={true}
- annotationsKey={""}
- fieldKey={Doc.LayoutFieldKey(this._document!)}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={returnFalse}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- ContentScaling={returnOne}
- PanelWidth={this.returnMiniSize}
- PanelHeight={this.returnMiniSize}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- renderDepth={0}
- whenActiveChanged={emptyFunction}
- focus={emptyFunction}
- backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
- addDocTab={this.addDocTab}
- pinToPres={DockedFrameRenderer.PinDoc}
- docFilters={returnEmptyFilter}
- fitToBox={true}
- />
- <div className="miniOverlay" onPointerDown={this.miniDown} >
- <div className="miniThumb" style={{
- width: `${this.miniWidth}%`,
- height: `${this.miniHeight}%`,
- left: `${this.miniLeft}%`,
- top: `${this.miniTop}%`,
- }}
- />
- </div>
+ return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef}>
+ {this.props.renderDepth > 0 ? "Nested dashboards can't be rendered" : (null)}
</div>;
}
- @computed get docView() {
- TraceMobx();
- if (!this._document) return (null);
- const document = this._document;
- const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
- return <>
- <DocumentView key={document[Id]}
- LibraryPath={this._libraryPath}
- Document={document}
- DataDoc={resolvedDataDoc}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={undefined}
- removeDocument={undefined}
- ContentScaling={this.contentScaling}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- NativeHeight={this.nativeHeight}
- NativeWidth={this.nativeWidth}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- renderDepth={0}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- focus={emptyFunction}
- backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
- addDocTab={this.addDocTab}
- pinToPres={DockedFrameRenderer.PinDoc}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
- </>;
- }
-
- render() {
- return (!this._isActive || !this.layoutDoc) ? (null) :
- (<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
- style={{
- transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
- height: this.layoutDoc && this.layoutDoc._fitWidth ? undefined : "100%",
- width: this.widthpercent
- }}>
- {this.docView}
- </div >);
- }
}
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
+
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); },
"opens up the inputted document on the right side of the screen", "(doc: any)");
-Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
+Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/CollectionDockingViewMenu.scss
index bc9cf4848..4157f0f7e 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/CollectionDockingViewMenu.scss
@@ -1,15 +1,14 @@
-.parentDocumentSelector-linkFlyout {
+
+.dockingViewButtonSelector {
div {
overflow: visible !important;
}
- .metadataEntry-outerDiv {
- overflow: hidden !important;
- pointer-events: all;
- }
+ display: inline-block;
+ width:100%;
+ height:100%;
}
-
-.parentDocumentSelector-flyout {
+.dockingViewButtonSelector-flyout {
position: relative;
z-index: 9999;
background-color: #eeeeee;
@@ -32,32 +31,4 @@
border-right: 0px;
border-left: 0px;
}
-}
-
-.parentDocumentSelector-button {
- pointer-events: all;
- position: relative;
- display: inline-block;
-
- svg {
- // width:20px !important;
- //height:20px;
- }
-}
-
-.parentDocumentSelector-metadata {
- pointer-events: auto;
- padding-right: 5px;
- width: 25px;
- display: inline-block;
-}
-
-.buttonSelector {
- div {
- overflow: visible !important;
- }
-
- display: inline-block;
- width:100%;
- height:100%;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingViewMenu.tsx b/src/client/views/collections/CollectionDockingViewMenu.tsx
new file mode 100644
index 000000000..1cab293a8
--- /dev/null
+++ b/src/client/views/collections/CollectionDockingViewMenu.tsx
@@ -0,0 +1,48 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { DocumentButtonBar } from "../DocumentButtonBar";
+import { DocumentView } from "../nodes/DocumentView";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+@observer
+export class CollectionDockingViewMenu extends React.Component<{ views: () => DocumentView[], Stack: any }> {
+ customStylesheet(styles: any) {
+ return {
+ ...styles,
+ panel: {
+ ...styles.panel,
+ minWidth: "100px"
+ },
+ };
+ }
+ _ref = React.createRef<HTMLDivElement>();
+
+ @computed get flyout() {
+ return (
+ <div className="dockingViewButtonSelector-flyout" title=" " ref={this._ref}>
+ <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
+ </div>
+ );
+ }
+
+ @observable _tooltipOpen: boolean = false;
+ render() {
+ return <Tooltip open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
+ <span className="dockingViewButtonSelector"
+ onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}
+ onPointerDown={action(e => {
+ this.props.views()[0]?.select(false);
+ this._tooltipOpen = false;
+ })} >
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
+ <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
+ </Flyout>
+ </span>
+ </Tooltip >;
+ }
+}
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index f5c4299a9..ca72b98a5 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -4,10 +4,12 @@
.collectionLinearView-outer {
overflow: visible;
height: 100%;
+ pointer-events: none;
.collectionLinearView {
display: flex;
height: 100%;
+ align-items: center;
>span {
background: $dark-color;
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index e1b07077e..9eaa02bf8 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -17,6 +17,7 @@ import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
import { Tooltip } from '@material-ui/core';
+import { all } from 'bluebird';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -89,6 +90,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}
}
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
}
@action
@@ -112,12 +114,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
const backgroundColor = StrCast(this.props.Document.backgroundColor, "black");
const color = StrCast(this.props.Document.color, "white");
- const menuOpener = <label htmlFor={`${guid}`} style={{
- background: backgroundColor === color ? "black" : backgroundColor,
- // width: "18px", height: "18px", fontSize: "12.5px",
- // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
- // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
- }}
+ const menuOpener = <label htmlFor={`${guid}`} style={{ pointerEvents: "all", cursor: "default", background: backgroundColor === color ? "black" : backgroundColor, }}
onPointerDown={e => e.stopPropagation()} >
<p>{BoolCast(this.props.Document.linearViewIsExpanded) ? "–" : "+"}</p>
</label>;
@@ -139,6 +136,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
const scalable = pair.layout.onClick || pair.layout.onDragStart;
return <div className={`collectionLinearView-docBtn` + (scalable ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
style={{
+ pointerEvents: "all",
width: scalable ? (nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize) : undefined,
height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
}} >
@@ -166,6 +164,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
@@ -192,9 +191,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
</span>
</Tooltip>
- {/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
- onClick={this.exitLongLinks} /> */}
-
</span> : null}
</div>
</div>;
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index cfec3a6bc..1af1a05aa 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -69,7 +69,7 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap
if (!this._initialLookupPending.get(id)) {
this._initialLookupPending.set(id, true);
setTimeout(() => {
- titleLoc && Doc.SetInPlace(doc, "title", titleLoc, true);
+ titleLoc && Doc.SetInPlace(doc, `${fieldKey}-address`, titleLoc, true);
this.respondToAddressChange(doc, fieldKey, address).then(() => this._initialLookupPending.delete(id));
});
}
@@ -114,12 +114,12 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap
}
}
- private renderMarker = (layout: Doc) => {
- const location = this.getLocation(layout, Doc.LayoutFieldKey(layout));
+ private renderMarker = (layout: Doc, fieldKey?: string) => {
+ const location = this.getLocation(layout, fieldKey || Doc.LayoutFieldKey(layout));
return !location ? (null) :
<Marker
key={layout[Id]}
- label={StrCast(layout.title)}
+ label={StrCast(layout[`${this.props.fieldKey}-address`])}
position={location}
onClick={() => this.markerClick(layout, location)}
icon={this.renderMarkerIcon(layout)}
@@ -250,7 +250,7 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap
}}
>
{this.reactiveContents}
- {mapLoc ? this.renderMarker(this.rootDoc) : undefined}
+ {mapLoc && StrCast(this.rootDoc[`${fieldKey}-mapCenter-address`]) ? this.renderMarker(this.rootDoc, `${fieldKey}-mapCenter`) : undefined}
</GeoMap>
</div>
</div>;
@@ -260,9 +260,10 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap
export default GoogleApiWrapper({
apiKey: process.env.GOOGLE_MAPS!,
- LoadingContainer: () => (
- <div className={"loadingWrapper"}>
+ LoadingContainer: () => {
+ console.log(process.env.GOOGLE_MAPS);
+ return <div className={"loadingWrapper"}>
<img className={"loadingGif"} src={"/assets/loading.gif"} />
- </div>
- )
+ </div>;
+ }
})(CollectionMapView) as any; \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index c772dcfe7..1c96f69bf 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -140,7 +140,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
addDocument = (value: string, shiftDown?: boolean) => {
this._createAliasSelected = false;
const key = StrCast(this.props.parent.props.Document._pivotField);
- const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value });
+ const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, title: value });
newDoc[key] = this.getValue(this.props.heading);
const docs = this.props.parent.childDocList;
return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument(newDoc);
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 8658212b7..e36e5caa7 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -229,14 +229,18 @@
.flexLabel {
margin-bottom: 0;
}
- }
- .collectionGridViewChrome-entryBox {
- width: 50%;
+ .collectionGridViewChrome-entryBox {
+ width: 50%;
+ color: black;
+ }
+
+ .collectionGridViewChrome-columnButton {
+ color: black;
+ }
}
}
-
.collectionStackingViewChrome-sort,
.collectionTreeViewChrome-sort {
display: flex;
@@ -311,6 +315,59 @@
}
}
+.webBox-urlEditor {
+ position: relative;
+ opacity: 0.9;
+ z-index: 9001;
+ transition: top .5s;
+
+ .urlEditor {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ padding-bottom: 10px;
+ overflow: hidden;
+ margin-top: 5px;
+ height: 35px;
+
+ .editorBase {
+ display: flex;
+
+ .editor-collapse {
+ transition: all .5s, opacity 0.3s;
+ position: absolute;
+ width: 40px;
+ transform-origin: top left;
+ }
+
+ .switchToText {
+ color: $main-accent;
+ }
+
+ .switchToText:hover {
+ color: $dark-color;
+ }
+ }
+
+ button:hover {
+ transform: scale(1);
+ }
+ }
+}
+
+.webpage-urlInput {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: black;
+ font-size: 10px;
+ letter-spacing: 2px;
+ outline-color: black;
+ background: rgb(238, 238, 238);
+ width: 100%;
+ min-width: 350px;
+ margin-right: 10px;
+ height: 100%;
+}
+
.collectionFreeFormMenu-cont {
display: inline-flex;
position: relative;
@@ -320,6 +377,7 @@
.antimodeMenu-button {
text-align: center;
display: block;
+ position: relative;
}
.color-previewI {
@@ -327,12 +385,15 @@
height: 20%;
bottom: 0;
position: absolute;
+ margin-left: 2px;
}
.color-previewII {
width: 80%;
height: 80%;
margin-left: 10%;
+ position: absolute;
+ bottom: 5;
}
.btn-group {
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5119ff6c9..b04c9c2eb 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -5,45 +5,48 @@ import { Tooltip } from "@material-ui/core";
import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { ColorState } from "react-color";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
import { Document } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
import { List } from "../../../fields/List";
import { ObjectField } from "../../../fields/ObjectField";
-import { RichTextField } from "../../../fields/RichTextField";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
+import { WebField } from "../../../fields/URLField";
import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { DragManager } from "../../util/DragManager";
+import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
import { undoBatch } from "../../util/UndoManager";
-import AntimodeMenu from "../AntimodeMenu";
+import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { EditableView } from "../EditableView";
-import GestureOverlay from "../GestureOverlay";
+import { GestureOverlay } from "../GestureOverlay";
import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
-import RichTextMenu from "../nodes/formattedText/RichTextMenu";
+import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
+import { PresBox } from "../nodes/PresBox";
import "./CollectionMenu.scss";
import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { TabDocView } from "./TabDocView";
@observer
-export default class CollectionMenu extends AntimodeMenu {
- static Instance: CollectionMenu;
+export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
+ @observable static Instance: CollectionMenu;
@observable SelectedCollection: DocumentView | undefined;
@observable FieldKey: string;
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
this.FieldKey = "";
- CollectionMenu.Instance = this;
+ runInAction(() => CollectionMenu.Instance = this);
this._canFade = false; // don't let the inking menu fade away
- this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
+ runInAction(() => this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true));
this.jumpTo(300, 300);
}
@@ -130,7 +133,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
_contentCommand = {
params: ["target", "source"], title: "set content",
script: "getProto(self.target).data = copyField(self.source);",
- immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
+ immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)),
initialize: emptyFunction,
};
_onClickCommand = {
@@ -161,9 +164,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
};
_viewCommand = {
params: ["target"], title: "bookmark view",
- script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];",
- immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
- initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
+ script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale']; gotoFrame(self.target, self['target-currentFrame']);",
+ immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; this.target._currentFrame = 0; }),
+ initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; button['target-currentFrame'] = this.target._currentFrame; },
};
_clusterCommand = {
params: ["target"], title: "fit content",
@@ -173,18 +176,22 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
};
_fitContentCommand = {
params: ["target"], title: "toggle clusters",
- script: "self.target.useClusters = !self.target.useClusters;",
- immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
+ script: "self.target._useClusters = !self.target._useClusters;",
+ immediate: undoBatch((source: Doc[]) => this.target._useClusters = !this.target._useClusters),
initialize: emptyFunction
};
_saveFilterCommand = {
params: ["target"], title: "save filter",
- script: "self.target._docFilters = copyField(self['target-docFilters']);",
- immediate: undoBatch((source: Doc[]) => this.target._docFilters = undefined),
- initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; },
+ script: `self.target._docFilters = compareLists(self['target-docFilters'],self.target._docFilters) ? undefined : copyField(self['target-docFilters']);
+ self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
+ immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }),
+ initialize: (button: Doc) => {
+ button['target-docFilters'] = Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters instanceof ObjectField ? ObjectField.MakeCopy(Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters as any as ObjectField) : undefined;
+ button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined;
+ },
};
- @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
+ @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
@computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
@computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
@computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; }
@@ -366,6 +373,92 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
}
else return false;
}
+ @computed
+ get pinButton() {
+ const targetDoc = this.selectedDoc;
+ const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
+ return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
+ <button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderRight: "1px solid gray" }}
+ onClick={e => TabDocView.PinDoc(targetDoc, isPinned)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" />
+ </button>
+ </Tooltip>;
+ }
+
+ @undoBatch
+ onAlias = () => {
+ if (this.selectedDoc && this.selectedDocumentView) {
+ // const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true);
+ // copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
+ // copy.y = NumCast(this.selectedDoc.y) + 30;
+ // this.selectedDocumentView.props.addDocument?.(copy);
+ const alias = Doc.MakeAlias(this.selectedDoc);
+ alias.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
+ alias.y = NumCast(this.selectedDoc.y) + 30;
+ this.selectedDocumentView.props.addDocument?.(alias);
+ }
+ }
+ private _dragRef = React.createRef<HTMLButtonElement>();
+
+ @observable _aliasDown = false;
+ onAliasButtonDown = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
+ }
+
+ @undoBatch
+ onAliasButtonMoved = (e: PointerEvent) => {
+ if (this._dragRef.current && this.selectedDoc) {
+ const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
+ const [left, top] = [e.clientX, e.clientY];
+ dragData.dropAction = "alias";
+ DragManager.StartDocumentDrag([this._dragRef.current], dragData, left, top, {
+ offsetX: dragData.offset[0],
+ offsetY: dragData.offset[1],
+ hideSource: false
+ });
+ return true;
+ }
+ return false;
+ }
+
+ @computed
+ get aliasButton() {
+ const targetDoc = this.selectedDoc;
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Tap or Drag to create an alias"}</div>} placement="top">
+ <button className="antimodeMenu-button" ref={this._dragRef} onPointerDown={this.onAliasButtonDown} onClick={this.onAlias}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="lg" />
+ </button>
+ </Tooltip>;
+ }
+
+ @computed
+ get pinWithViewButton() {
+ const targetDoc = this.selectedDoc;
+ if (targetDoc) {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ }
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin to presentation with current view"}</div></>} placement="top">
+ <button className="antidmodeMenu-button" style={{ borderRight: "1px solid gray" }}
+ onClick={e => {
+ if (targetDoc) {
+ TabDocView.PinDoc(targetDoc, false);
+ const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeDoc.presPinView = true;
+ activeDoc.presPinViewX = x;
+ activeDoc.presPinViewY = y;
+ activeDoc.presPinViewScale = scale;
+ }
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-marker" />
+ </button>
+ </Tooltip>;
+ }
+
render() {
return (
@@ -391,6 +484,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
<FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} />
</button>
</Tooltip>}
+ {this.aliasButton}
+ {this.pinButton}
+ {this.pinWithViewButton}
</div>
{this.subChrome}
</div>
@@ -429,7 +525,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
@computed get isText() {
if (this.selectedDoc) {
- return this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField;
+ const layoutField = Doc.LayoutField(this.selectedDoc);
+ return StrCast(layoutField).includes("FormattedText") ||
+ (layoutField instanceof Doc && StrCast(layoutField.layout).includes("FormattedText"));
}
else return false;
}
@@ -437,25 +535,25 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@undoBatch
@action
nextKeyframe = (): void => {
- const currentFrame = Cast(this.document.currentFrame, "number", null);
+ const currentFrame = Cast(this.document._currentFrame, "number", null);
if (currentFrame === undefined) {
- this.document.currentFrame = 0;
+ this.document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
- this.document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
- this.document.lastFrame = Math.max(NumCast(this.document.currentFrame), NumCast(this.document.lastFrame));
+ this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
}
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = Cast(this.document.currentFrame, "number", null);
+ const currentFrame = Cast(this.document._currentFrame, "number", null);
if (currentFrame === undefined) {
- this.document.currentFrame = 0;
+ this.document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
- this.document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
}
@undoBatch
@action
@@ -605,6 +703,141 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>;
}
+ onUrlDrop = (e: React.DragEvent) => {
+ const { dataTransfer } = e;
+ const html = dataTransfer.getData("text/html");
+ const uri = dataTransfer.getData("text/uri-list");
+ const url = uri || html || this._url;
+ const newurl = url.startsWith(window.location.origin) ?
+ url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+ this.submitURL(newurl);
+ e.stopPropagation();
+ }
+ onUrlDragover = (e: React.DragEvent) => {
+ e.preventDefault();
+ }
+
+ @computed get _url() {
+ return this.selectedDoc?.data instanceof WebField ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : Field.toString(this.selectedDoc?.data as Field);
+ }
+
+ set _url(value) {
+ if (this.selectedDoc) {
+ Doc.GetProto(this.selectedDoc).data = new WebField(value);
+ Doc.SetInPlace(this.selectedDoc, "title", value, true);
+ const annots = Doc.GetProto(this.selectedDoc)["data-annotations-" + this.urlHash(value)];
+ Doc.GetProto(this.selectedDoc)["data-annotations"] = annots instanceof ObjectField ? ObjectField.MakeCopy(annots) : new List<Doc>([]);
+ }
+ }
+
+ @action
+ submitURL = (url: string) => {
+ if (!url.startsWith("http")) url = "http://" + url;
+ try {
+ const selectedDoc = this.selectedDoc;
+ if (selectedDoc) {
+ const URLy = new URL(url);
+ const future = Cast(selectedDoc["data-future"], listSpec("string"), null);
+ const history = Cast(selectedDoc["data-history"], listSpec("string"), null);
+ const annos = DocListCast(selectedDoc["data-annotations"]);
+ if (Field.toString(selectedDoc.data as Field) === Field.toString(new WebField(URLy))) {
+ Doc.GetProto(selectedDoc).data = undefined;
+ setTimeout(action(() => Doc.GetProto(selectedDoc).data = new WebField(URLy)), 0);
+ } else {
+ if (url) {
+ Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(annos);
+ if (history === undefined) {
+ selectedDoc["data-history"] = new List<string>([this._url]);
+ } else {
+ history.push(this._url);
+ }
+ future && (future.length = 0);
+ }
+ this._url = url;
+ }
+ }
+ } catch (e) {
+ console.log("WebBox URL error:" + url);
+ }
+ }
+
+ urlHash(s: string) {
+ return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
+ }
+
+ onValueKeyDown = async (e: React.KeyboardEvent) => {
+ e.key === "Enter" && this.submitURL(this._keyInput.current!.value);
+ e.stopPropagation();
+ }
+
+ @action
+ forward = () => {
+ const selectedDoc = this.selectedDoc;
+ if (selectedDoc) {
+ const future = Cast(selectedDoc["data-future"], listSpec("string"), null);
+ const history = Cast(selectedDoc["data-history"], listSpec("string"), null);
+ if (future?.length) {
+ history?.push(this._url);
+ Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(selectedDoc["data-annotations"]));
+ const newurl = future.pop()!;
+ Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl));
+ Doc.GetProto(selectedDoc)["data-annotations"] = new List<Doc>(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)]));
+ }
+ }
+ }
+
+ @action
+ back = () => {
+ const selectedDoc = this.selectedDoc;
+ if (selectedDoc) {
+ const future = Cast(selectedDoc["data-future"], listSpec("string"), null);
+ const history = Cast(selectedDoc["data-history"], listSpec("string"), null);
+ if (history?.length) {
+ if (future === undefined) selectedDoc["data-future"] = new List<string>([this._url]);
+ else future.push(this._url);
+ Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(selectedDoc["data-annotations"]));
+ const newurl = history.pop()!;
+ Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl));
+ Doc.GetProto(selectedDoc)["data-annotations"] = new List<Doc>(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)]));
+ }
+ }
+ }
+
+ private _keyInput = React.createRef<HTMLInputElement>();
+
+ @computed get urlEditor() {
+ return (
+ <div className="webBox-buttons"
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover} style={{ display: "flex" }}>
+ <input className="webpage-urlInput" key={this._url}
+ placeholder="ENTER URL"
+ defaultValue={this._url}
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover}
+ onKeyDown={this.onValueKeyDown}
+ onClick={(e) => {
+ this._keyInput.current!.select();
+ e.stopPropagation();
+ }}
+ ref={this._keyInput}
+ />
+ <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", maxWidth: "250px", }}>
+ <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
+ GO
+ </button>
+ <button className="submitUrl" onClick={this.back}>
+ <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
+ </button>
+ <button className="submitUrl" onClick={this.forward}>
+ <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
+ </button>
+ </div>
+ </div>
+ );
+ }
+
+
@observable viewType = this.selectedDoc?._viewType;
render() {
@@ -625,7 +858,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
{!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
<div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }}
onClick={action(() => this.document.editing = !this.document.editing)} >
- {NumCast(this.document.currentFrame)}
+ {NumCast(this.document._currentFrame)}
</div>
</Tooltip> : null}
{!this.isText && !this.props.isDoc ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom">
@@ -635,16 +868,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</Tooltip> : null}
{!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) :
- <Tooltip key="hypothesis" title={<div className="dash-tooltip">Use Hypothesis</div>} placement="bottom">
- <button className={"antimodeMenu-button"} key="hypothesis"
- style={{
- backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined,
- borderRight: "1px solid gray"
- }}
- onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}>
- <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} />
- </button>
- </Tooltip>
+ this.urlEditor
}
{!this.isText ?
<>
@@ -655,7 +879,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</> :
(null)
}
- {this.isText ? <RichTextMenu key="rich" /> : null}
+ {this.isText ? <RichTextMenu /> : null}
</div>;
}
}
@@ -677,15 +901,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
if (docs instanceof Doc) {
const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
- (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_"));
+ (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key[0] !== "_"));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
} else {
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 ||
- key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 ||
- key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] &&
- key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_"));
+ const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
+ key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
+ (key[0]?.toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key[0] !== "_"));
return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
}
@@ -933,16 +1156,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
get numCols() { return NumCast(this.document.gridNumCols, 10); }
/**
- * Sets the value of `numCols` on the grid's Document to the value entered.
- */
- @undoBatch
- onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter" || e.key === "Tab") {
- if (e.currentTarget.valueAsNumber > 0) {
- this.document.gridNumCols = e.currentTarget.valueAsNumber;
- }
-
- }
+ * Sets the value of `numCols` on the grid's Document to the value entered.
+ */
+ onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ if (e.currentTarget.valueAsNumber > 0) undoBatch(() => this.document.gridNumCols = e.currentTarget.valueAsNumber)();
}
/**
@@ -980,9 +1197,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
onDecrementButtonClick = () => {
this.clicked = true;
- if (!this.decrementLimitReached) {
+ if (this.numCols > 1 && !this.decrementLimitReached) {
this.entered && (this.document.gridNumCols as number)++;
undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
+ if (this.numCols === 1) this.decrementLimitReached = true;
}
this.entered = false;
}
@@ -1005,7 +1223,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
decrementValue = () => {
this.entered = true;
if (!this.clicked) {
- if (this.numCols !== 1) {
+ if (this.numCols > 1) {
this.document.gridNumCols = this.numCols - 1;
}
else {
@@ -1038,9 +1256,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
<span className="grid-icon">
<FontAwesomeIcon icon="columns" size="1x" />
</span>
- <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
- <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
- <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
+ <input className="collectionGridViewChrome-entryBox" type="number" value={this.numCols} onChange={this.onNumColsChange} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
+ <input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
</span>
{/* <span className="grid-control">
<span className="grid-icon">
@@ -1081,4 +1299,15 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
</div>
);
}
-} \ No newline at end of file
+}
+Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) {
+ const dataField = doc[Doc.LayoutFieldKey(doc)];
+ const childDocs = DocListCast(dataField);
+ const currentFrame = Cast(doc._currentFrame, "number", null);
+ if (currentFrame === undefined) {
+ doc._currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+ doc._currentFrame = Math.max(0, newFrame);
+}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 2e4055256..522f93b88 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -28,10 +28,8 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}
this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
this.layoutDoc._chromeStatus = "disabled";
- this.layoutDoc.hideFilterView = true;
}
componentWillUnmount() {
- this.layoutDoc.hideFilterView = false;
this.layoutDoc._chromeStatus = this._originalChrome;
}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 20ce6b76d..18ae260e8 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -1,44 +1,38 @@
import React = require("react");
-import { action, observable, trace, computed, runInAction } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
+import DatePicker from "react-datepicker";
+import "react-datepicker/dist/react-datepicker.css";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter, Utils, emptyPath } from "../../../Utils";
+import { DateField } from "../../../fields/DateField";
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";
-import { Transform } from "../../util/Transform";
-import { MAX_ROW_HEIGHT, COLLECTION_BORDER_WIDTH } from '../globalCssVariables.scss';
-import '../DocumentDecorations.scss';
-import { EditableView } from "../EditableView";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import "./CollectionSchemaView.scss";
-import { CollectionView, Flyout } from "./CollectionView";
-import { NumCast, StrCast, BoolCast, FieldValue, Cast, DateCast } from "../../../fields/Types";
-import { Docs } from "../../documents/Documents";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faExpand } from '@fortawesome/free-solid-svg-icons';
+import { List } from "../../../fields/List";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { undoBatch } from "../../util/UndoManager";
-import { SnappingManager } from "../../util/SnappingManager";
import { ComputedField } from "../../../fields/ScriptField";
+import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
import { ImageField } from "../../../fields/URLField";
-import { List } from "../../../fields/List";
-import { OverlayView } from "../OverlayView";
-import { DocumentIconContainer } from "../nodes/DocumentIcon";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import DatePicker from "react-datepicker";
-import "react-datepicker/dist/react-datepicker.css";
-import { DateField } from "../../../fields/DateField";
-import { RichTextField } from "../../../fields/RichTextField";
+import { Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager } from "../../util/DragManager";
+import { KeyCodes } from "../../util/KeyCodes";
+import { CompileScript } from "../../util/Scripting";
import { SearchUtil } from "../../util/SearchUtil";
+import { SnappingManager } from "../../util/SnappingManager";
+import { undoBatch } from "../../util/UndoManager";
+import '../DocumentDecorations.scss';
+import { EditableView } from "../EditableView";
+import { MAX_ROW_HEIGHT } from '../globalCssVariables.scss';
+import { DocumentIconContainer } from "../nodes/DocumentIcon";
+import { OverlayView } from "../OverlayView";
+import "./CollectionSchemaView.scss";
+import { CollectionView } from "./CollectionView";
const path = require('path');
-library.add(faExpand);
-
export interface CellProps {
row: number;
col: number;
@@ -64,37 +58,24 @@ export interface CellProps {
@observer
export class CollectionSchemaCell extends React.Component<CellProps> {
+ public static resolvedFieldKey(column: string, rowDoc: Doc) {
+ const fieldKey = column;
+ if (fieldKey.startsWith("*")) {
+ const rootKey = fieldKey.substring(1);
+ const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))];
+ const matchedKeys = allKeys.filter(key => key.includes(rootKey));
+ if (matchedKeys.length) return matchedKeys[0];
+ }
+ return fieldKey;
+ }
@observable protected _isEditing: boolean = false;
protected _focusRef = React.createRef<HTMLDivElement>();
- protected _document = this.props.rowProps.original;
+ protected _rowDoc = this.props.rowProps.original;
protected _dropDisposer?: DragManager.DragDropDisposer;
-
- async componentWillMount() {
-
- }
-
- async componentDidMount() {
- document.addEventListener("keydown", this.onKeyDown);
- console.log("mounted");
- console.log(this.type);
- if (this.type === "context") {
- console.log("mounted2");
- const doc = Doc.GetProto(this.props.rowProps.original);
- const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc);
- if (aliasdoc.length > 0) {
- const targetContext = Cast(aliasdoc[0].context, Doc) as Doc;
- console.log(StrCast(targetContext.title));
- runInAction(() => this.contents = StrCast(targetContext.title));
- }
- }
-
- }
-
@observable contents: string = "";
- componentWillUnmount() {
- document.removeEventListener("keydown", this.onKeyDown);
- }
+ componentDidMount() { document.addEventListener("keydown", this.onKeyDown); }
+ componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown); }
@action
onKeyDown = (e: KeyboardEvent): void => {
@@ -116,7 +97,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@action
onPointerDown = async (e: React.PointerEvent): Promise<void> => {
-
+ this.onItemDown(e);
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
@@ -130,61 +111,39 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
} catch { }
}
- // this._isEditing = true;
- // this.props.setIsEditing(true);
-
- const field = this.props.rowProps.original[this.props.rowProps.column.id!];
- const doc = FieldValue(Cast(field, Doc));
- if (typeof field === "object" && doc) this.props.setPreviewDoc(doc);
+ const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null);
+ doc && this.props.setPreviewDoc(doc);
}
@undoBatch
applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
if (!res.success) return false;
- // doc[this.props.fieldKey] = res.result;
- // return true;
- doc[this.props.rowProps.column.id as string] = res.result;
+ doc[this.renderFieldKey] = res.result;
return true;
}
private drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
- const fieldKey = this.props.rowProps.column.id as string;
if (de.complete.docDragData.draggedDocuments.length === 1) {
- this._document[fieldKey] = de.complete.docDragData.draggedDocuments[0];
+ this._rowDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0];
}
else {
const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {});
- this._document[fieldKey] = coll;
+ this._rowDoc[this.renderFieldKey] = coll;
}
e.stopPropagation();
}
}
protected dropRef = (ele: HTMLElement | null) => {
- this._dropDisposer && this._dropDisposer();
- if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
- }
+ this._dropDisposer?.();
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
- // expandDoc = (e: React.PointerEvent) => {
- // let field = this.props.rowProps.original[this.props.rowProps.column.id as string];
- // let doc = FieldValue(Cast(field, Doc));
-
- // this.props.setPreviewDoc(doc!);
-
- // // this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
-
- // e.stopPropagation();
- // }
-
- returnHighlights(bing: (() => string), positions?: number[]) {
- const results = [];
- const contents = bing();
-
- if (positions !== undefined) {
+ returnHighlights(contents: string, positions?: number[]) {
+ if (positions) {
+ const results = [];
StrCast(this.props.Document._searchString);
const length = StrCast(this.props.Document._searchString).length;
const color = contents ? "black" : "grey";
@@ -199,71 +158,24 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
);
return results;
}
- else {
- return <span style={{ color: contents ? "black" : "grey" }}>{contents ? contents?.valueOf() : "undefined"}</span>;
- }
+ return <span style={{ color: contents ? "black" : "grey" }}>{contents ? contents?.valueOf() : "undefined"}</span>;
}
- type: string = "";
+ @computed get renderFieldKey() { return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); }
+ onItemDown = async (e: React.PointerEvent) => {
+ if (this.props.Document._searchDoc) {
+ const doc = Doc.GetProto(this._rowDoc);
+ const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc);
+ const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
+ DocumentManager.Instance.jumpToDocument(this._rowDoc, false, () => undefined, targetContext);
+ }
+ }
renderCellWithType(type: string | undefined) {
const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
- const props: FieldViewProps = {
- Document: this.props.rowProps.original,
- DataDoc: this.props.rowProps.original,
- LibraryPath: [],
- dropAction: "alias",
- bringToFront: emptyFunction,
- rootSelected: returnFalse,
- fieldKey: this.props.rowProps.column.id as string,
- docFilters: returnEmptyFilter,
- ContainingCollectionView: this.props.CollectionView,
- ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
- isSelected: returnFalse,
- select: emptyFunction,
- renderDepth: this.props.renderDepth + 1,
- ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
- active: returnFalse,
- whenActiveChanged: emptyFunction,
- PanelHeight: returnZero,
- PanelWidth: returnZero,
- NativeHeight: returnZero,
- NativeWidth: returnZero,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- ContentScaling: returnOne
- };
-
- let matchedKeys = [props.fieldKey];
- if (props.fieldKey.startsWith("*")) {
- const allKeys = Array.from(Object.keys(props.Document));
- allKeys.push(...Array.from(Object.keys(Doc.GetProto(props.Document))));
- matchedKeys = allKeys.filter(key => key.includes(props.fieldKey.substring(1)));
- }
- const fieldKey = matchedKeys.length ? matchedKeys[0] : props.fieldKey;
- const field = props.Document[fieldKey];
- const doc = FieldValue(Cast(field, Doc));
- const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
+ const fieldKey = this.renderFieldKey;
+ const field = this._rowDoc[fieldKey];
- const onItemDown = async (e: React.PointerEvent) => {
- if (this.props.Document._searchDoc !== undefined) {
- const doc = Doc.GetProto(this.props.rowProps.original);
- const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc);
- let targetContext = undefined;
- if (aliasdoc.length > 0) {
- targetContext = Cast(aliasdoc[0].context, Doc) as Doc;
- }
- DocumentManager.Instance.jumpToDocument(this.props.rowProps.original, false, undefined, targetContext);
- }
- else {
- fieldIsDoc &&
- SetupDrag(this._focusRef,
- () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
- 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 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) {
dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
@@ -273,52 +185,24 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
dragRef.current!.className = "collectionSchemaView-cellContainer";
};
- let contents: any = "incorrect type";
- if (type === undefined) contents = <FieldView {...props} fieldKey={fieldKey} />;
- if (type === "number") contents = typeof field === "number" ? NumCast(field) : "--" + typeof field + "--";
- if (type === "string") contents = typeof field === "string" ? (StrCast(field) === "" ? "--" : StrCast(field)) : "--" + typeof field + "--";
- if (type === "boolean") contents = typeof field === "boolean" ? (BoolCast(field) ? "true" : "false") : "--" + typeof field + "--";
- if (type === "document") {
- const doc = FieldValue(Cast(field, Doc));
- contents = typeof field === "object" ? doc ? StrCast(doc.title) === "" ? "--" : StrCast(doc.title) : `--${typeof field}--` : `--${typeof field}--`;
- }
- if (type === "image") {
- const image = FieldValue(Cast(field, ImageField));
- const doc = FieldValue(Cast(field, Doc));
- contents = typeof field === "object" ? doc ? StrCast(doc.title) === "" ? "--" : StrCast(doc.title) : `--${typeof field}--` : `--${typeof field}--`;
- }
- if (type === "list") {
- contents = typeof field === "object" ? doc ? StrCast(field) === "" ? "--" : StrCast(field) : `--${typeof field}--` : `--${typeof field}--`;
- }
- if (type === "date") {
- contents = typeof field === "object" ? doc ? StrCast(field) === "" ? "--" : StrCast(field) : `--${typeof field}--` : `--${typeof field}--`;
- }
-
+ let contents = Field.toString(field as Field);
+ contents = contents === "" ? "--" : contents;
let className = "collectionSchemaView-cellWrapper";
if (this._isEditing) className += " editing";
if (this.props.isFocused && this.props.isEditable) className += " focused";
if (this.props.isFocused && !this.props.isEditable) className += " inactive";
-
- // let docExpander = (
- // <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} >
- // <FontAwesomeIcon icon="expand" size="sm" />
- // </div>
- // );
const positions = [];
- let cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
- this.type = props.fieldKey;
if (StrCast(this.props.Document._searchString).toLowerCase() !== "") {
- let term = Field.toString(cfield as Field);
- term = term.toLowerCase();
+ let term = (field instanceof Promise) ? "...promise pending..." : contents.toLowerCase();
const search = StrCast(this.props.Document._searchString).toLowerCase();
let start = term.indexOf(search);
let tally = 0;
if (start !== -1) {
positions.push(start);
}
- while (start < contents.length && start !== -1) {
+ while (start < contents?.length && start !== -1) {
term = term.slice(start + search.length + 1);
tally += start + search.length + 1;
start = term.indexOf(search);
@@ -328,179 +212,94 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
positions.pop();
}
}
- let search = false;
- if (this.props.Document._searchDoc !== undefined) {
- search = true;
- }
-
+ const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined";
return (
- <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }}
+ <div className="collectionSchemaView-cellContainer" style={{ cursor: field instanceof Doc ? "grab" : "auto" }}
ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
- <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}>
- <div className="collectionSchemaView-cellContents"
- ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}>
- {!search ?
+ <div className={className} ref={this._focusRef} tabIndex={-1}>
+ <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null}>
+ {!this.props.Document._searchDoc ?
<EditableView
- positions={positions.length > 0 ? positions : undefined}
- search={StrCast(this.props.Document._searchString) ? StrCast(this.props.Document._searchString) : undefined}
editing={this._isEditing}
isEditingCallback={this.isEditingCallback}
display={"inline"}
- contents={contents ? contents : type === "number" ? "0" : "undefined"}
- highlight={positions.length > 0 ? true : undefined}
- //contents={StrCast(contents)}
+ contents={contents}
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
- placeholder={"undefined"}
- bing={() => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
- if (cfield !== undefined) {
- // if (typeof(cfield)===RichTextField)
- const a = cfield as RichTextField;
- const b = cfield as DateField;
- console.log(b);
- if (a.Text !== undefined) {
- return (a.Text);
- }
- else if (b.toString() !== undefined) {
- return b.toString();
- }
- else if (StrCast(cfield)) {
- return StrCast(cfield);
- }
- else {
- return String(NumCast(cfield));
- }
- }
- }}
+ placeholder={placeholder}
GetValue={() => {
- if (type === "number" && (contents === 0 || contents === "0")) {
- return "0";
- } else {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
- if (type === "number") {
- return StrCast(cfield);
- }
- const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
- const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
- const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) :
- Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
- return val;
-
- }
-
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(field));
+ const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
+ const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
+ return cscript ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) :
+ Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
}}
SetValue={action((value: string) => {
let retVal = false;
-
- if (value.startsWith(":=")) {
- retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ if (value.startsWith(":=") || value.startsWith("=:=")) {
+ const script = value.substring(value.startsWith("=:=") ? 3 : 2);
+ retVal = this.props.setComputed(script, value.startsWith(":=") ? Doc.GetProto(this.props.Document) : this.props.Document, this.renderFieldKey, this.props.row, this.props.col);
} else {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- if (script.compiled) {
- retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
- }
-
+ script.compiled && (retVal = this.applyToDoc(this._rowDoc, this.props.row, this.props.col, script.run));
}
if (retVal) {
this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
this.props.setIsEditing(false);
}
return retVal;
-
- //return true;
})}
OnFillDown={async (value: string) => {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- if (script.compiled) {
- DocListCast(this.props.Document[this.props.fieldKey]).
- forEach((doc, i) => value.startsWith(":=") ?
- this.props.setComputed(value.substring(2), doc, this.props.rowProps.column.id!, i, this.props.col) :
- this.applyToDoc(doc, i, this.props.col, script.run));
- }
+ script.compiled && DocListCast(field).
+ forEach((doc, i) => value.startsWith(":=") ?
+ this.props.setComputed(value.substring(2), doc, this.renderFieldKey, i, this.props.col) :
+ this.applyToDoc(doc, i, this.props.col, script.run));
}}
/>
:
- this.returnHighlights(() => {
- console.log(props.fieldKey);
- const dateCheck: Date | undefined = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date : undefined;
- if (dateCheck !== undefined) {
- cfield = dateCheck.toLocaleString();
- }
- if (props.fieldKey === "context") {
- cfield = this.contents;
- console.log("this should work");
- }
- if (props.fieldKey === "*lastModified") {
- if (FieldValue(props.Document["data-lastModified"]) !== undefined) {
- const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["data-lastModified"])) as DateField;
- cfield = d.date.toLocaleString();
- }
-
- else if (FieldValue(props.Document["text-lastModified"]) !== undefined) {
- const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["text-lastModified"])) as DateField;
- cfield = d.date.toLocaleString();
- }
- }
- return Field.toString(cfield as Field);
- }, positions)
+ this.returnHighlights(contents, positions)
}
</div >
- {/* {fieldIsDoc ? docExpander : null} */}
</div>
</div>
);
}
- render() {
- return this.renderCellWithType(undefined);
- }
+ render() { return this.renderCellWithType(undefined); }
}
@observer
-export class CollectionSchemaNumberCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType("number");
- }
-}
+export class CollectionSchemaNumberCell extends CollectionSchemaCell { render() { return this.renderCellWithType("number"); } }
@observer
-export class CollectionSchemaBooleanCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType("boolean");
- }
-}
+export class CollectionSchemaBooleanCell extends CollectionSchemaCell { render() { return this.renderCellWithType("boolean"); } }
@observer
-export class CollectionSchemaStringCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType("string");
- }
-}
+export class CollectionSchemaStringCell extends CollectionSchemaCell { render() { return this.renderCellWithType("string"); } }
@observer
export class CollectionSchemaDateCell extends CollectionSchemaCell {
- @observable private _date: Date = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date :
- this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof Date ? this.props.rowProps.original[this.props.rowProps.column.id as string] : new Date();
+ @computed get _date(): Opt<DateField> { return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; }
@action
handleChange = (date: any) => {
- this._date = date;
// const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
// if (script.compiled) {
// this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
- this._document[this.props.rowProps.column.id as string] = date as Date;
+ this._rowDoc[this.renderFieldKey] = new DateField(date as Date);
//}
}
render() {
- return <DatePicker
- selected={this._date}
- onSelect={date => this.handleChange(date)}
- onChange={date => this.handleChange(date)}
- />;
+ return !this.props.isFocused ? <span onPointerDown={this.onPointerDown}>{this._date ? Field.toString(this._date as Field) : "--"}</span> :
+ <DatePicker
+ selected={this._date?.date || new Date}
+ onSelect={date => this.handleChange(date)}
+ onChange={date => this.handleChange(date)}
+ />;
}
}
@@ -509,44 +308,11 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
_overlayDisposer?: () => void;
- private prop: FieldViewProps = {
- Document: this.props.rowProps.original,
- DataDoc: this.props.rowProps.original,
- LibraryPath: [],
- dropAction: "alias",
- bringToFront: emptyFunction,
- rootSelected: returnFalse,
- fieldKey: this.props.rowProps.column.id as string,
- ContainingCollectionView: this.props.CollectionView,
- ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
- isSelected: returnFalse,
- select: emptyFunction,
- renderDepth: this.props.renderDepth + 1,
- ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
- active: returnFalse,
- whenActiveChanged: emptyFunction,
- PanelHeight: returnZero,
- PanelWidth: returnZero,
- NativeHeight: returnZero,
- NativeWidth: returnZero,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- ContentScaling: returnOne,
- docFilters: returnEmptyFilter
- };
- @observable private _field = this.prop.Document[this.prop.fieldKey];
- @observable private _doc = FieldValue(Cast(this._field, Doc));
- @observable private _docTitle = this._doc?.title;
- @observable private _preview = false;
- @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
- @computed get tableWidth() { return this.prop.PanelWidth() - 2 * this.borderWidth - 4 - this.previewWidth(); }
+ @computed get _doc() { return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); }
@action
onSetValue = (value: string) => {
- this._docTitle = value;
- //this.prop.Document[this.prop.fieldKey] = this._text;
+ this._doc && (Doc.GetProto(this._doc).title = value);
const script = CompileScript(value, {
addReturn: true,
@@ -556,46 +322,22 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
const results = script.compiled && script.run();
if (results && results.success) {
- this._doc = results.result;
- this._document[this.prop.fieldKey] = results.result;
- this._docTitle = this._doc?.title;
-
+ this._rowDoc[this.renderFieldKey] = results.result;
return true;
}
return false;
}
+ componentWillUnmount() { this.onBlur(); }
+
+ onBlur = () => { this._overlayDisposer?.(); };
onFocus = () => {
- this._overlayDisposer?.();
+ this.onBlur();
this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
}
@action
- onOpenClick = () => {
- this._preview = false;
- if (this._doc) {
- this.props.addDocTab(this._doc, "onRight");
- return true;
- }
- return false;
- }
-
- @action
- showPreview = (bool: boolean, e: any) => {
- if (this._isEditing) {
- this._preview = false;
- } else {
- if (bool) {
- this.props.showDoc(this._doc, this.prop.DataDoc, e.clientX, e.clientY);
- } else {
- this.props.showDoc(undefined);
- }
- }
- }
-
- @action
- isEditingCalling = (isEditing: boolean): void => {
- this.showPreview(false, "");
+ isEditingCallback = (isEditing: boolean): void => {
document.removeEventListener("keydown", this.onKeyDown);
isEditing && document.addEventListener("keydown", this.onKeyDown);
this._isEditing = isEditing;
@@ -603,227 +345,90 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
}
- onDown = (e: any) => {
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- this.props.setPreviewDoc(this.props.rowProps.original);
-
- let url: string;
- if (url = StrCast(this.props.rowProps.row.href)) {
- try {
- new URL(url);
- const temp = window.open(url)!;
- temp.blur();
- window.focus();
- } catch { }
- }
-
- const field = this.props.rowProps.original[this.props.rowProps.column.id!];
- const doc = FieldValue(Cast(field, Doc));
- if (typeof field === "object" && doc) this.props.setPreviewDoc(doc);
-
- this.showPreview(true, e);
-
- }
-
render() {
- if (typeof this._field === "object" && this._doc && this._docTitle) {
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1}
- onPointerDown={(e) => { this.onDown(e); }}
- onPointerEnter={(e) => { this.showPreview(true, e); }}
- onPointerLeave={(e) => { this.showPreview(false, e); }}
+ return !this._doc ? this.renderCellWithType("document") :
+ <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1}
+ onPointerDown={this.onPointerDown}
+ >
+ <div className="collectionSchemaView-cellContents-document"
+ style={{ padding: "5.9px" }}
+ ref={this.dropRef}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
>
-
- <div className="collectionSchemaView-cellContents-document"
- style={{ padding: "5.9px" }}
- ref={this.dropRef}
- onFocus={this.onFocus}
- onBlur={() => this._overlayDisposer?.()}
- >
-
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCalling}
- display={"inline"}
- contents={this._docTitle}
- height={"auto"}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- GetValue={() => {
- return StrCast(this._docTitle);
- }}
- SetValue={action((value: string) => {
- this.onSetValue(value);
- this.showPreview(false, "");
- return true;
- })}
- />
- </div >
- <div onClick={this.onOpenClick} className="collectionSchemaView-cellContents-docButton">
- <FontAwesomeIcon icon="external-link-alt" size="lg" ></FontAwesomeIcon> </div>
+ <EditableView
+ editing={this._isEditing}
+ isEditingCallback={this.isEditingCallback}
+ display={"inline"}
+ contents={this._doc.title || "--"}
+ height={"auto"}
+ maxHeight={Number(MAX_ROW_HEIGHT)}
+ GetValue={() => StrCast(this._doc?.title)}
+ SetValue={action((value: string) => {
+ this.onSetValue(value);
+ return true;
+ })}
+ />
+ </div >
+ <div onClick={() => this._doc && this.props.addDocTab(this._doc, "add:right")} className="collectionSchemaView-cellContents-docButton">
+ <FontAwesomeIcon icon="external-link-alt" size="lg" />
</div>
- );
- } else {
- return this.renderCellWithType("document");
- }
+ </div>;
}
}
@observer
export class CollectionSchemaImageCell extends CollectionSchemaCell {
- // render() {
- // return this.renderCellWithType("image");
- // }
- choosePath(url: URL, dataDoc: any) {
- const lower = url.href.toLowerCase();
- if (url.protocol === "data") {
- return url.href;
- } else if (url.href.indexOf(window.location.origin) === -1) {
- return Utils.CorsProxy(url.href);
- } else if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) {
- return url.href;//Why is this here
- }
+ choosePath(url: URL) {
+ if (url.protocol === "data") return url.href;
+ if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href);
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here
+
const ext = path.extname(url.href);
- const _curSuffix = "_o";
- return url.href.replace(ext, _curSuffix + ext);
+ return url.href.replace(ext, "_o" + path.extname(url.href));
}
render() {
- const props: FieldViewProps = {
- Document: this.props.rowProps.original,
- DataDoc: this.props.rowProps.original,
- LibraryPath: [],
- dropAction: "alias",
- bringToFront: emptyFunction,
- rootSelected: returnFalse,
- fieldKey: this.props.rowProps.column.id as string,
- ContainingCollectionView: this.props.CollectionView,
- ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
- isSelected: returnFalse,
- select: emptyFunction,
- renderDepth: this.props.renderDepth + 1,
- ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
- active: returnFalse,
- whenActiveChanged: emptyFunction,
- PanelHeight: returnZero,
- PanelWidth: returnZero,
- NativeHeight: returnZero,
- NativeWidth: returnZero,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- ContentScaling: returnOne,
- docFilters: returnEmptyFilter
- };
-
- let image = true;
- let url = [];
- if (props.DataDoc) {
- const field = Cast(props.DataDoc[props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(props.DataDoc[props.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
- const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url, props.DataDoc)); // access the primary layout data of the alternate documents
- const paths = field ? [this.choosePath(field.url, props.DataDoc), ...altpaths] : altpaths;
- if (paths.length) {
- url = paths;
- } else {
- url = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
- image = false;
- }
- //url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
- } else {
- url = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
- image = false;
- }
-
- const heightToWidth = NumCast(props.DataDoc?._nativeHeight) / NumCast(props.DataDoc?._nativeWidth);
- const height = this.props.rowProps.width * heightToWidth;
+ const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this._rowDoc[this.renderFieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
+ const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
+ const url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
- if (props.fieldKey === "data") {
- if (url !== []) {
- const reference = React.createRef<HTMLDivElement>();
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}>
- <img src={url[0]} width={image ? this.props.rowProps.width : "30px"}
- height={image ? height : "30px"} />
- </div >
- </div>
- );
+ const heightToWidth = NumCast(this._rowDoc._nativeHeight) / NumCast(this._rowDoc._nativeWidth);
+ let width = Math.min(75, this.props.rowProps.width);
+ const height = Math.min(75, width * heightToWidth);
+ width = height / heightToWidth;
- } else {
- return this.renderCellWithType("image");
- }
- } else {
- return this.renderCellWithType("image");
- }
+ const reference = React.createRef<HTMLDivElement>();
+ return <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+ <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
+ <img src={url[0]}
+ width={paths.length ? width : "20px"}
+ height={paths.length ? height : "20px"} />
+ </div >
+ </div>;
}
}
-
-
-
@observer
export class CollectionSchemaListCell extends CollectionSchemaCell {
-
_overlayDisposer?: () => void;
- private prop: FieldViewProps = {
- Document: this.props.rowProps.original,
- DataDoc: this.props.rowProps.original,
- LibraryPath: [],
- dropAction: "alias",
- bringToFront: emptyFunction,
- rootSelected: returnFalse,
- fieldKey: this.props.rowProps.column.id as string,
- ContainingCollectionView: this.props.CollectionView,
- ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
- isSelected: returnFalse,
- select: emptyFunction,
- renderDepth: this.props.renderDepth + 1,
- ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
- active: returnFalse,
- whenActiveChanged: emptyFunction,
- PanelHeight: returnZero,
- PanelWidth: returnZero,
- NativeHeight: returnZero,
- NativeWidth: returnZero,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- ContentScaling: returnOne,
- docFilters: returnEmptyFilter
- };
- @observable private _field = this.prop.Document[this.prop.fieldKey];
- @observable private _optionsList = this._field as List<any>;
+ @computed get _field() { return this._rowDoc[this.renderFieldKey]; }
+ @computed get _optionsList() { return this._field as List<any>; }
@observable private _opened = false;
@observable private _text = "select an item";
@observable private _selectedNum = 0;
@action
- toggleOpened(open: boolean) {
- this._opened = open;
- }
-
- // @action
- // onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
- // this._text = e.target.value;
-
- // // change if its a document
- // this._optionsList[this._selectedNum] = this._text;
- // }
-
- @action
onSetValue = (value: string) => {
-
-
- this._text = value;
-
// change if its a document
- this._optionsList[this._selectedNum] = this._text;
-
- (this.prop.Document[this.prop.fieldKey] as List<any>).splice(this._selectedNum, 1, value);
+ this._optionsList[this._selectedNum] = this._text = value;
+ (this._field as List<any>).splice(this._selectedNum, 1, value);
}
@action
@@ -837,55 +442,23 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
}
-
render() {
-
- const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- let type = "list";
-
- let link = false;
- let doc = false;
+ const link = false;
const reference = React.createRef<HTMLDivElement>();
- if (typeof this._field === "object" && this._optionsList[0]) {
-
- const options = this._optionsList.map((element, index) => {
-
- if (element instanceof Doc) {
- doc = true;
- type = "document";
- if (this.prop.fieldKey.toLowerCase() === "links") {
- link = true;
- type = "link";
- }
- const document = FieldValue(Cast(element, Doc));
- const title = element.title;
- return <div
- className="collectionSchemaView-dropdownOption"
- onPointerDown={(e) => { this.onSelected(StrCast(element.title), index); }}
- style={{ padding: "6px" }}>
- {title}
- </div>;
-
- } else {
- return <div
- className="collectionSchemaView-dropdownOption"
- onPointerDown={(e) => { this.onSelected(StrCast(element), index); }}
- style={{ padding: "6px" }}>{element}</div>;
- }
- });
+ if (this._optionsList?.length) {
+ const options = !this._opened ? (null) :
+ <div>
+ {this._optionsList.map((element, index) => {
+ const val = Field.toString(element);
+ return <div className="collectionSchemaView-dropdownOption" key={index} style={{ padding: "6px" }} onPointerDown={(e) => this.onSelected(StrCast(element), index)} >
+ {val}
+ </div>;
+ })}
+ </div>;
const plainText = <div style={{ padding: "5.9px" }}>{this._text}</div>;
- // const textarea = <textarea onChange={this.onChange} value={this._text}
- // onFocus={doc ? this.onFocus : undefined}
- // onBlur={doc ? e => this._overlayDisposer?.() : undefined}
- // style={{ resize: "none" }}
- // placeholder={"select an item"}></textarea>;
-
- const textarea = <div className="collectionSchemaView-cellContents"
- style={{ padding: "5.9px" }}
- ref={type === undefined || type === "document" ? this.dropRef : null} key={this.prop.Document[Id]}>
+ const textarea = <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} style={{ padding: "5.9px" }} ref={this.dropRef} >
<EditableView
editing={this._isEditing}
isEditingCallback={this.isEditingCallback}
@@ -893,11 +466,8 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
contents={this._text}
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
- GetValue={() => {
- return this._text;
- }}
+ GetValue={() => this._text}
SetValue={action((value: string) => {
-
// add special for params
this.onSetValue(value);
return true;
@@ -906,61 +476,35 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
</div >;
//☰
-
- const dropdown = <div>
- {options}
- </div>;
-
return (
<div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}>
+ <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
<div className="collectionSchemaView-dropDownWrapper">
- <button type="button" className="collectionSchemaView-dropdownButton" onClick={(e) => { this.toggleOpened(!this._opened); }}
- style={{ right: "length", position: "relative" }}>
- {this._opened ? <FontAwesomeIcon icon="caret-up" size="lg" ></FontAwesomeIcon>
- : <FontAwesomeIcon icon="caret-down" size="lg" ></FontAwesomeIcon>}
+ <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: "length", position: "relative" }}
+ onClick={action(e => this._opened = !this._opened)} >
+ <FontAwesomeIcon icon={this._opened ? "caret-up" : "caret-down"} size="sm" />
</button>
<div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
</div>
-
- {this._opened ? dropdown : null}
+ {options}
</div >
</div>
);
- } else {
- return this.renderCellWithType("list");
}
+ return this.renderCellWithType("list");
}
}
-
-
-
@observer
export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
- @observable private _isChecked: boolean = typeof this.props.rowProps.original[this.props.rowProps.column.id as string] === "boolean" ? BoolCast(this.props.rowProps.original[this.props.rowProps.column.id as string]) : false;
-
- @action
- toggleChecked = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._isChecked = e.target.checked;
- const script = CompileScript(e.target.checked.toString(), { requiredType: "boolean", addReturn: true, params: { this: Doc.name } });
- if (script.compiled) {
- this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
- }
- }
+ @computed get _isChecked() { return BoolCast(this._rowDoc[this.renderFieldKey]); }
render() {
const reference = React.createRef<HTMLDivElement>();
- const onItemDown = (e: React.PointerEvent) => {
- (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
- SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
- };
return (
<div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={this._document[Id]} ref={reference}>
- <input type="checkbox" checked={this._isChecked} onChange={this.toggleChecked} />
- </div >
+ <input type="checkbox" checked={this._isChecked} onChange={e => this._rowDoc[this.renderFieldKey] = e.target.checked} />
</div>
);
}
@@ -969,62 +513,15 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
@observer
export class CollectionSchemaButtons extends CollectionSchemaCell {
-
render() {
- // const reference = React.createRef<HTMLDivElement>();
- // const onItemDown = (e: React.PointerEvent) => {
- // (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
- // SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
- // };
- const doc = this.props.rowProps.original;
- let buttons: JSX.Element | undefined = undefined;
- buttons = <div style={{
- paddingTop: 8,
- paddingLeft: 3,
- }}><button onClick={() => {
- doc.searchMatch = false;
- setTimeout(() => doc.searchMatch = true, 0);
- }} style={{ padding: 2, left: 77 }}>
- <FontAwesomeIcon icon="arrow-up" size="sm" />
- </button>
- <button onClick={() => {
- {
- doc.searchMatchAlt = false;
- setTimeout(() => doc.searchMatchAlt = true, 0);
- }
- }} style={{ padding: 2 }}>
- <FontAwesomeIcon icon="arrow-down" size="sm" />
- </button></div>;
- const type = StrCast(doc.type);
- if (type === "pdf") {
- buttons = <div><button
- style={{
- position: "relative",
- height: 30,
- width: 28,
- left: 1,
- }}
-
- onClick={() => {
- doc.searchMatch = false;
- setTimeout(() => doc.searchMatch = true, 0);
- }}>
- <FontAwesomeIcon icon="arrow-down" size="sm" />
- </button></div >;
- }
- else if (type !== "rtf") {
- buttons = undefined;
- }
-
- if (BoolCast(this.props.Document._searchDoc) === true) {
-
- }
- else {
- buttons = undefined;
- }
- return (
- <div> {buttons}</div>
- );
+ return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <></> :
+ <div style={{ paddingTop: 8, paddingLeft: 3 }} >
+ <button style={{ padding: 2, left: 77 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, true)}>
+ <FontAwesomeIcon icon="arrow-up" size="sm" />
+ </button>
+ <button style={{ padding: 2 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, false)} >
+ <FontAwesomeIcon icon="arrow-down" size="sm" />
+ </button>
+ </div>;
}
-}
-
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 5c0e6581b..d2a1234ed 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -13,57 +13,11 @@ import { SearchBox } from "../search/SearchBox";
import { ColumnType } from "./CollectionSchemaView";
import "./CollectionSchemaView.scss";
import { CollectionView } from "./CollectionView";
-import * as fa from '@fortawesome/free-solid-svg-icons';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-export interface HeaderProps {
- keyValue: SchemaHeaderField;
- possibleKeys: string[];
- existingKeys: string[];
- keyType: ColumnType;
- typeConst: boolean;
- onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
- setIsEditing: (isEditing: boolean) => void;
- deleteColumn: (column: string) => void;
- setColumnType: (column: SchemaHeaderField, type: ColumnType) => void;
- setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void;
- setColumnColor: (column: SchemaHeaderField, color: string) => void;
-
-}
-
-export class CollectionSchemaHeader extends React.Component<HeaderProps> {
- render() {
- const icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" :
- this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "sort-amount-down" :
- this.props.keyType === ColumnType.Image ? "image" : this.props.keyType === ColumnType.List ? "list-ul" : this.props.keyType === ColumnType.Date ? "calendar" :
- "align-justify";
- return (
- <div className="collectionSchemaView-header" style={{ background: this.props.keyValue.color }}>
- <CollectionSchemaColumnMenu
- columnField={this.props.keyValue}
- // keyValue={this.props.keyValue.heading}
- possibleKeys={this.props.possibleKeys}
- existingKeys={this.props.existingKeys}
- // keyType={this.props.keyType}
- typeConst={this.props.typeConst}
- menuButtonContent={<div><FontAwesomeIcon icon={icon} size="sm" />{this.props.keyValue.heading}</div>}
- addNew={false}
- onSelect={this.props.onSelect}
- setIsEditing={this.props.setIsEditing}
- deleteColumn={this.props.deleteColumn}
- onlyShowOptions={false}
- setColumnType={this.props.setColumnType}
- setColumnSort={this.props.setColumnSort}
- setColumnColor={this.props.setColumnColor}
- />
- </div>
- );
- }
-}
-
export interface AddColumnHeaderProps {
createColumn: () => void;
@@ -79,7 +33,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe
}
-
export interface ColumnMenuProps {
columnField: SchemaHeaderField;
// keyValue: string;
@@ -103,37 +56,29 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
@observable private _isOpen: boolean = false;
@observable private _node: HTMLDivElement | null = null;
- componentDidMount() {
- document.addEventListener("pointerdown", this.detectClick);
- }
+ componentDidMount() { document.addEventListener("pointerdown", this.detectClick); }
- componentWillUnmount() {
- document.removeEventListener("pointerdown", this.detectClick);
- }
+ componentWillUnmount() { document.removeEventListener("pointerdown", this.detectClick); }
- detectClick = (e: PointerEvent): void => {
- if (this._node && this._node.contains(e.target as Node)) {
- } else {
- this._isOpen = false;
- this.props.setIsEditing(false);
- }
+ @action
+ detectClick = (e: PointerEvent) => {
+ !this._node?.contains(e.target as Node) && this.props.setIsEditing(this._isOpen = false);
}
@action
toggleIsOpen = (): void => {
- this._isOpen = !this._isOpen;
- this.props.setIsEditing(this._isOpen);
+ this.props.setIsEditing(this._isOpen = !this._isOpen);
}
- changeColumnType = (type: ColumnType): void => {
+ changeColumnType = (type: ColumnType) => {
this.props.setColumnType(this.props.columnField, type);
}
- changeColumnSort = (desc: boolean | undefined): void => {
+ changeColumnSort = (desc: boolean | undefined) => {
this.props.setColumnSort(this.props.columnField, desc);
}
- changeColumnColor = (color: string): void => {
+ changeColumnColor = (color: string) => {
this.props.setColumnColor(this.props.columnField, color);
}
@@ -145,7 +90,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
}
renderTypes = () => {
- if (this.props.typeConst) return <></>;
+ if (this.props.typeConst) return (null);
const type = this.props.columnField.type;
return (
@@ -291,7 +236,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
@observable private _key: string = this.props.keyValue;
@observable private _searchTerm: string = this.props.keyValue;
@observable private _isOpen: boolean = false;
- @observable private _canClose: boolean = true;
+ @observable private _node: HTMLDivElement | null = null;
@observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef();
@action setSearchTerm = (value: string): void => { this._searchTerm = value; };
@@ -306,6 +251,31 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
this.props.setIsEditing(false);
}
+ @action
+ setNode = (node: HTMLDivElement): void => {
+ if (node) {
+ this._node = node;
+ }
+ }
+
+ componentDidMount() {
+ document.addEventListener("pointerdown", this.detectClick);
+ const filters = Cast(this.props.Document._docFilters, listSpec("string"));
+ if (filters?.includes(this._key)) {
+ runInAction(() => this.closeResultsVisibility = "contents");
+ }
+ }
+
+ @action
+ detectClick = (e: PointerEvent): void => {
+ if (this._node && this._node.contains(e.target as Node)) {
+ } else {
+ this._isOpen = false;
+ this.props.setIsEditing(false);
+ }
+ }
+
+ private tempfilter: string = "";
@undoBatch
onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === "Enter") {
@@ -313,23 +283,27 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
const colpos = this._searchTerm.indexOf(":");
const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
if (temp === "") {
- Doc.setDocFilter(this.props.Document, this._key, temp, undefined);
+ Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined);
+ this.updateFilter();
}
else {
- Doc.setDocFilter(this.props.Document, this._key, temp, "match");
+ Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined);
+ this.tempfilter = temp;
+ Doc.setDocFilter(this.props.Document, this._key, temp, "check");
this.props.col.setColor("green");
+ this.closeResultsVisibility = "contents";
}
}
else {
+ Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined);
+ this.updateFilter();
let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const blockedkeys = ["_scrollTop", "customTitle", "limitHeight", "proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"];
- keyOptions = keyOptions.filter(n => !blockedkeys.includes(n));
+ const blockedkeys = ["system", "title-custom", "limitHeight", "proto", "x", "y", "zIndex", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"];
+ keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL"));
if (keyOptions.length) {
this.onSelect(keyOptions[0]);
- console.log("case1");
} else if (this._searchTerm !== "" && this.props.canAddNew) {
this.setSearchTerm(this._searchTerm || this._key);
- console.log("case2");
this.onSelect(this._searchTerm);
}
}
@@ -347,23 +321,6 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
}
@action
- onBlur = (e: React.FocusEvent): void => {
- if (this._canClose) {
- this._isOpen = false;
- this.props.setIsEditing(false);
- }
- }
-
- @action
- onPointerEnter = (e: React.PointerEvent): void => {
- this._canClose = false;
- }
-
- @action
- onPointerOut = (e: React.PointerEvent): void => {
- this._canClose = true;
- }
- @action
renderOptions = (): JSX.Element[] | JSX.Element => {
if (!this._isOpen) {
this.defaultMenuHeight = 0;
@@ -375,21 +332,26 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
const exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 ||
this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
- const blockedkeys = ["proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"];
- keyOptions = keyOptions.filter(n => !blockedkeys.includes(n));
+ const blockedkeys = ["proto", "x", "y", "zIndex", "_timeStampOnEnter", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"];
+ keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL"));
const options = keyOptions.map(key => {
return <div key={key} className="key-option" style={{
border: "1px solid lightgray",
width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
}}
- onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect(key); this.setSearchTerm(""); }}>{key}</div>;
+ onPointerDown={e => {
+ e.stopPropagation();
+ }}
+ onClick={() => {
+ this.onSelect(key);
+ this.setSearchTerm("");
+ }}>{key}</div>;
});
// if search term does not already exist as a group type, give option to create new group type
if (this._key !== this._searchTerm.slice(0, this._key.length)) {
- console.log("little further");
if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
options.push(<div key={""} className="key-option" style={{
border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
@@ -418,55 +380,55 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
@action
renderFilterOptions = (): JSX.Element[] | JSX.Element => {
- if (!this._isOpen) {
+ if (!this._isOpen || !this.props.dataDoc) {
this.defaultMenuHeight = 0;
return <></>;
}
-
const keyOptions: string[] = [];
const colpos = this._searchTerm.indexOf(":");
const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
if (this.docSafe.length === 0) {
- this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]);
+ this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]);
}
const docs = this.docSafe;
docs.forEach((doc) => {
const key = StrCast(doc[this._key]);
- if (keyOptions.includes(key) === false && key.includes(temp)) {
+ if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") {
keyOptions.push(key);
}
});
const filters = Cast(this.props.Document._docFilters, listSpec("string"));
+ if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) {
+ this.props.col.setColor("rgb(241, 239, 235)");
+ this.closeResultsVisibility = "none";
+ }
for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) {
if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) {
keyOptions.push(filters![i + 1]);
}
}
-
- if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) {
- this.props.col.setColor("rgb(241, 239, 235)");
- }
-
const options = keyOptions.map(key => {
- //Doc.setDocFilter(this.props.Document!, this._key, key, undefined);
let bool = false;
- console.log(filters);
if (filters !== undefined) {
bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check";
- console.log(filters.includes(key));
}
return <div key={key} className="key-option" style={{
border: "1px solid lightgray", paddingLeft: 5, textAlign: "left",
width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white",
}}
>
- <input type="checkbox" onChange={(e) => {
- e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, undefined);
- e.target.checked === true ? this.props.col.setColor("green") : "";
- e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, undefined);
- }}
- checked={bool} ></input>
+ <input type="checkbox"
+ onPointerDown={e => e.stopPropagation()}
+ onClick={e => e.stopPropagation()}
+ onChange={(e) => {
+ e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, undefined);
+ e.target.checked === true ? this.closeResultsVisibility = "contents" : console.log("");
+ e.target.checked === true ? this.props.col.setColor("green") : this.updateFilter();
+ e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, undefined);
+ }}
+ checked={bool}
+ />
<span style={{ paddingLeft: 4 }}>
{key}
</span>
@@ -492,41 +454,67 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
@observable defaultMenuHeight = 0;
+ updateFilter() {
+ const filters = Cast(this.props.Document._docFilters, listSpec("string"));
+ if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) {
+ this.props.col.setColor("rgb(241, 239, 235)");
+ this.closeResultsVisibility = "none";
+ }
+ }
+
get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; }
@computed get scriptField() {
- console.log("we kinda made it");
const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
return script ? () => script : undefined;
}
filterBackground = () => "rgba(105, 105, 105, 0.432)";
-
@observable filterOpen: boolean | undefined = undefined;
+ closeResultsVisibility: string = "none";
+
+ removeFilters = (e: React.PointerEvent): void => {
+ const keyOptions: string[] = [];
+ if (this.docSafe.length === 0 && this.props.dataDoc) {
+ this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]);
+ }
+ const docs = this.docSafe;
+ docs.forEach((doc) => {
+ const key = StrCast(doc[this._key]);
+ if (keyOptions.includes(key) === false) {
+ keyOptions.push(key);
+ }
+ });
+
+ Doc.setDocFilter(this.props.Document, this._key, "", "remove");
+ this.props.col.setColor("rgb(241, 239, 235)");
+ this.closeResultsVisibility = "none";
+ }
render() {
return (
- <div style={{ display: "flex" }}>
- <FontAwesomeIcon onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} />
+ <div style={{ display: "flex" }} ref={this.setNode}>
+ <FontAwesomeIcon onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} />
{/* <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => {
runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen })
}} /> */}
- <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}>
+ <div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
<input className="keys-search" style={{ width: "100%" }}
ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown}
onChange={e => this.onChange(e.target.value)}
- onClick={(e) => {
- //this._inputRef.current!.select();
- e.stopPropagation();
- }} onFocus={this.onFocus} onBlur={this.onBlur}></input>
- <div className="keys-options-wrapper" style={{
+ onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }}
+ onFocus={this.onFocus} ></input>
+ <div style={{ display: this.closeResultsVisibility }}>
+ <FontAwesomeIcon onPointerDown={this.removeFilters} icon={"times-circle"} size="lg"
+ style={{ cursor: "hand", color: "grey", padding: 2, left: -20, top: -1, height: 15, position: "relative" }} />
+ </div>
+ {!this._isOpen ? (null) : <div className="keys-options-wrapper" style={{
width: this.props.width, maxWidth: this.props.width, height: "auto",
- }}
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}>
+ }}>
{this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()}
- </div>
+ </div>}
</div >
</div>
);
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index dade4f2f2..881246bd4 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -1,21 +1,17 @@
import React = require("react");
-import { ReactTableDefaults, TableCellRenderer, RowInfo } from "react-table";
-import "./CollectionSchemaView.scss";
-import { Transform } from "../../util/Transform";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action } from "mobx";
+import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
import { Doc } from "../../../fields/Doc";
-import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
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 "../../../fields/SchemaHeaderField";
-import { undoBatch } from "../../util/UndoManager";
+import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SnappingManager } from "../../util/SnappingManager";
-
-library.add(faGripVertical, faTrash);
+import { Transform } from "../../util/Transform";
+import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import "./CollectionSchemaView.scss";
export interface MovableColumnProps {
columnRenderer: TableCellRenderer;
@@ -40,7 +36,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
onPointerLeave = (e: React.PointerEvent): void => {
this._header!.current!.className = "collectionSchema-col-wrapper";
document.removeEventListener("pointermove", this.onDragMove, true);
- document.removeEventListener("pointermove", this.onPointerMove);
+ !e.buttons && document.removeEventListener("pointermove", this.onPointerMove);
}
onDragMove = (e: PointerEvent): void => {
const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
@@ -68,6 +64,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
const before = x[0] < bounds[0];
const colDragData = de.complete.columnDragData;
if (colDragData) {
+ e.stopPropagation();
this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
return true;
}
@@ -108,8 +105,10 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
this._dragRef = ref;
const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
- this._startDragPosition = { x: dx, y: dy };
- document.addEventListener("pointermove", this.onPointerMove);
+ if (!(e.target as any)?.tagName.includes("INPUT")) {
+ this._startDragPosition = { x: dx, y: dy };
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
}
@@ -164,6 +163,10 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (!before) this._header!.current!.className += " row-below";
e.stopPropagation();
}
+ componentWillUnmount() {
+
+ this._rowDropDisposer?.();
+ }
createRowDropTarget = (ele: HTMLDivElement) => {
this._rowDropDisposer?.();
@@ -198,6 +201,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
}
onRowContextMenu = (e: React.MouseEvent): void => {
+ e.preventDefault();
const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row";
ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" });
}
@@ -219,13 +223,15 @@ export class MovableRow extends React.Component<MovableRowProps> {
render() {
const { children = null, rowInfo } = this.props;
+
if (!rowInfo) {
return <ReactTableDefaults.TrComponent>{children}</ReactTableDefaults.TrComponent>;
}
const { original } = rowInfo;
const doc = FieldValue(Cast(original, Doc));
- if (!doc) return <></>;
+
+ if (!doc) return (null);
const reference = React.createRef<HTMLDivElement>();
const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
@@ -238,11 +244,11 @@ export class MovableRow extends React.Component<MovableRowProps> {
<div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
<div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
<ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} >
- {/* <div className="row-dragger">
- <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
- <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
- <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
- </div> */}
+ <div className="row-dragger">
+ <div className="row-option" style={{ left: 5 }} onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
+ <div className="row-option" style={{ cursor: "grab", left: 25 }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
+ <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+ </div>
{children}
</ReactTableDefaults.TrComponent>
</div>
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 5c2931a8b..8d2f645d9 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -59,6 +59,7 @@
justify-content: space-between;
flex-wrap: nowrap;
touch-action: none;
+ padding: 2px;
div {
touch-action: none;
@@ -99,6 +100,9 @@
direction: ltr;
overflow: visible;
}
+ .rt-noData {
+ display: none;
+ }
.rt-thead {
width: 100%;
@@ -139,6 +143,10 @@
width: 100%;
direction: rtl;
overflow: visible;
+
+ .rt-td {
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ }
}
.rt-tr-group {
@@ -158,6 +166,8 @@
font-size: 13px;
text-align: center;
white-space: nowrap;
+ display: flex;
+ align-items: center;
.imageBox-cont {
position: relative;
@@ -176,6 +186,11 @@
height: 100%;
}
}
+ .rt-td.rt-expandable {
+ display: flex;
+ align-items: center;
+ height: inherit;
+ }
.rt-resizer {
width: 8px;
@@ -211,7 +226,8 @@
height: auto;
z-index: 100;
position: absolute;
- background:white;
+ background: white;
+ padding: 5px;
.collectionSchema-header-toggler {
z-index: 100;
@@ -243,12 +259,13 @@ button.add-column {
.collectionSchema-header-menuOptions {
color: black;
- width: 200px;
+ width: 180px;
text-align: left;
.collectionSchema-headerMenu-group {
padding: 7px 0;
border-bottom: 1px solid lightgray;
+ cursor: pointer;
&:first-child {
padding-top: 0;
@@ -326,6 +343,7 @@ button.add-column {
background-color: white;
border: 1px solid lightgray;
padding: 2px 3px;
+
&:not(:first-child) {
border-top: 0;
}
@@ -356,47 +374,6 @@ button.add-column {
}
}
-.altcollectionTimeView-treeView {
- display: flex;
- flex-direction: column;
- width: 175px;
- height: auto;
- position: fixed;
- border-left: solid 1px;
- z-index: 1;
-
- .collectionTimeView-addfacet {
- display: inline-block;
- width: 200px;
- height: 30px;
- background: darkGray;
- text-align: left;
-
- .collectionTimeView-button {
- align-items: center;
- display: flex;
- width: 100%;
- height: 100%;
-
- .collectionTimeView-span {
- margin: auto;
- }
- }
-
- >div,
- >div>div {
- width: 100%;
- height: 100%;
- }
- }
-
- .altcollectionTimeView-tree {
- display: inline-block;
- width: 100%;
- height: calc(100% - 30px);
- }
-}
-
.collectionSchema-row {
height: 100%;
background-color: white;
@@ -414,11 +391,12 @@ button.add-column {
.row-dragger {
display: flex;
justify-content: space-around;
- flex: 50 0 auto;
- width: 50px;
+ //flex: 50 0 auto;
+ width: 0;
max-width: 50px;
- height: 100%;
+ //height: 100%;
min-height: 30px;
+ align-items: center;
color: lightgray;
background-color: white;
transition: color 0.1s ease;
@@ -426,10 +404,12 @@ button.add-column {
.row-option {
// padding: 5px;
cursor: pointer;
+ position: absolute;
transition: color 0.1s ease;
display: flex;
flex-direction: column;
justify-content: center;
+ z-index: 2;
&:hover {
color: gray;
@@ -459,15 +439,15 @@ button.add-column {
.collectionSchemaView-cellContainer {
width: 100%;
- height: 100%;
+ height: unset;
}
.collectionSchemaView-cellWrapper {
height: 100%;
padding: 4px;
- text-align:left;
- padding-left:19px;
-
+ text-align: left;
+ padding-left: 19px;
+
position: relative;
&:focus {
@@ -596,21 +576,23 @@ button.add-column {
.collectionSchemaView-table {
width: 100%;
height: 100%;
+ overflow: auto;
+ padding: 3px;
}
+.rt-td.rt-expandable {
+ overflow: visible;
+ position: relative;
+ height:100%;
+ z-index: 1;
+}
.reactTable-sub {
- padding: 10px 30px;
background-color: rgb(252, 252, 252);
width: 100%;
.rt-thead {
- display:none;
- }
- .collectionSchemaView-table{
- border: solid 1px;
- overflow: hidden;
+ display: none;
}
-
.row-dragger {
background-color: rgb(252, 252, 252);
@@ -621,20 +603,26 @@ button.add-column {
}
.collectionSchemaView-table {
- width: 100%;
+ width: 100%;
+ border: solid 1px;
+ overflow: visible;
+ padding: 0px;
}
}
.collectionSchemaView-expander {
height: 100%;
min-height: 30px;
- position: relative;
+ position: absolute;
color: gray;
+ width: 20;
+ height: auto;
+ left: 55;
svg {
position: absolute;
top: 50%;
- left: 50%;
+ left: 10;
transform: translate(-50%, -50%);
}
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f1de3cee7..1b68c0e1a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,32 +1,29 @@
import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, untracked } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import { Resize } from "react-table";
import "react-table/react-table.css";
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { Cast, NumCast, BoolCast } from "../../../fields/Types";
+import { Cast, NumCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
import '../DocumentDecorations.scss';
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { KeysDropdown } from "./CollectionSchemaHeaders";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { SchemaTable } from "./SchemaTable";
-
-library.add(faCog, faPlus, faSortUp, faSortDown);
-library.add(faTable);
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
export enum ColumnType {
@@ -44,7 +41,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
["title", ColumnType.String],
["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number],
["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+ ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
]);
@observer
@@ -62,8 +59,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@observable _menuWidth = 0;
@observable _headerOpen = false;
- @observable _isOpen = false;
- @observable _node: HTMLDivElement | null = null;
@observable _headerIsEditing = false;
@observable _col: any = "";
@observable _menuHeight = 0;
@@ -73,7 +68,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed get menuCoordinates() {
let searchx = 0;
let searchy = 0;
- if (this.props.Document._searchDoc !== undefined) {
+ if (this.props.Document._searchDoc) {
const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0];
if (el !== undefined) {
const rect = el.getBoundingClientRect();
@@ -86,7 +81,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return this.props.ScreenToLocalTransform().transformPoint(x, y);
}
- @observable scale = this.props.ScreenToLocalTransform().Scale;
+ @computed get scale() { return this.props.ScreenToLocalTransform().Scale; }
@computed get columns() {
return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
@@ -111,33 +106,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
@computed get possibleKeys() { return this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); }
+ @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
- componentDidMount() {
- document.addEventListener("pointerdown", this.detectClick);
- }
-
- componentWillUnmount() {
- document.removeEventListener("pointerdown", this.detectClick);
- }
-
- @action setHeaderIsEditing = (isEditing: boolean) => {
- this._headerIsEditing = isEditing;
- }
-
- detectClick = (e: PointerEvent): void => {
- if (this._node && this._node.contains(e.target as Node)) {
- } else {
- this._isOpen = false;
- this.setHeaderIsEditing(false);
- this.closeHeader();
- }
- }
-
- @action
- toggleIsOpen = (): void => {
- this._isOpen = !this._isOpen;
- this.setHeaderIsEditing(this._isOpen);
- }
@action
changeColumnType = (type: ColumnType, col: any): void => {
@@ -190,16 +160,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this.columns = columns;
}
- @action
- setNode = (node: HTMLDivElement): void => {
- node && (this._node = node);
- }
-
- @action
- typesDropdownChange = (bool: boolean) => {
- this._openTypes = bool;
- }
-
renderTypes = (col: any) => {
if (columnTypes.get(col.heading)) return (null);
@@ -263,10 +223,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
type === ColumnType.Date ? dateType : imageType;
return (
- <div className="collectionSchema-headerMenu-group">
- <div onClick={() => this.typesDropdownChange(!this._openTypes)}>
- <label>Column type:</label>
- <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right" }} />
+ <div className="collectionSchema-headerMenu-group" onClick={action(() => this._openTypes = !this._openTypes)}>
+ <div>
+ <label style={{ cursor: "pointer" }}>Column type:</label>
+ <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right", transform: `rotate(${this._openTypes ? "180deg" : 0})`, transition: "0.2s all ease" }} />
</div>
{this._openTypes ? allColumnTypes : justColType}
</div >
@@ -340,17 +300,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this.columns = columns;
if (filter) {
Doc.setDocFilter(this.props.Document, newKey, filter, "match");
- if (this.props.Document.selectedDoc !== undefined) {
- const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc;
- Doc.setDocFilter(doc, newKey, filter, "match");
- }
}
else {
this.props.Document._docFilters = undefined;
- if (this.props.Document.selectedDoc !== undefined) {
- const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc;
- doc._docFilters = undefined;
- }
}
}
}
@@ -360,7 +312,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
openHeader = (col: any, screenx: number, screeny: number) => {
this._col = col;
- this._headerOpen = !this._headerOpen;
+ this._headerOpen = true;
this._pointerX = screenx;
this._pointerY = screeny;
}
@@ -392,7 +344,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
onHeaderClick = (e: React.PointerEvent) => {
- this.props.active(true);
e.stopPropagation();
}
@@ -408,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
TraceMobx();
return <div className="collectionSchema-header-menuOptions">
{this.renderTypes(this._col)}
- {this.renderSorting(this._col)}
+ {/* {this.renderSorting(this._col)} */}
{this.renderColors(this._col)}
<div className="collectionSchema-headerMenu-group">
<button onClick={() => { this.deleteColumn(this._col.heading); }}
@@ -426,7 +377,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action setFocused = (doc: Doc) => this._focusedTable = doc;
- @action setPreviewDoc = (doc: Doc) => this.previewDoc = doc;
+ @action setPreviewDoc = (doc: Opt<Doc>) => {
+ SelectionManager.SelectSchemaDoc(this, doc);
+ this.previewDoc = doc;
+ }
//toggles preview side-panel of schema
@action
@@ -487,6 +441,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
PanelHeight={this.previewHeight}
ScreenToLocalTransform={this.getPreviewTransform}
docFilters={this.docFilters}
+ searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
moveDocument={this.props.moveDocument}
@@ -531,6 +486,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
documentKeys={this.documentKeys}
headerIsEditing={this._headerIsEditing}
openHeader={this.openHeader}
+ onClick={e => { e.stopPropagation(); this.closeHeader(); }}
onPointerDown={this.onTablePointerDown}
onResizedChange={this.onResizedChange}
setColumns={this.setColumns}
@@ -552,15 +508,29 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
</div>
</div>;
}
+ onSpecificMenu = (e: React.MouseEvent) => {
+ if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) {
+ const cm = ContextMenu.Instance;
+ const options = cm.findByDescription("Options...");
+ const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+ optionItems.push({ description: "remove", event: () => this.previewDoc && this.props.removeDocument(this.previewDoc), icon: "trash" });
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+ cm.displayMenu(e.clientX, e.clientY);
+ (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
+ e.stopPropagation();
+ }
+ }
@action
onTablePointerDown = (e: React.PointerEvent): void => {
+ if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) {
+ this.setPreviewDoc(undefined);
+ }
this.setFocused(this.props.Document);
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected(true)) {
e.stopPropagation();
}
- this._pointerY = e.screenY;
- this._pointerX = e.screenX;
+ // this.closeHeader();
}
onResizedChange = (newResized: Resize[], event: any) => {
@@ -610,17 +580,18 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
render() {
let name = "collectionSchemaView-container";
- if (this.props.Document._searchDoc !== undefined) {
+ if (this.props.Document._searchDoc) {
name = "collectionSchemaView-searchContainer";
}
+ if (!this.props.active()) setTimeout(() => this.closeHeader(), 0);
TraceMobx();
const menuContent = this.renderMenuContent;
- const menu = <div className="collectionSchema-header-menu" ref={this.setNode}
+ const menu = <div className="collectionSchema-header-menu"
onWheel={e => this.onZoomMenu(e)}
onPointerDown={e => this.onHeaderClick(e)}
style={{
position: "fixed", background: "white", border: "black 1px solid",
- transform: `translate(${(this.menuCoordinates[0] / this.scale)}px, ${(this.menuCoordinates[1] / this.scale)}px)`
+ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)`
}}>
<Measure offset onResize={action((r: any) => {
const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
@@ -631,13 +602,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
</div>;
return <div className={name}
style={{
- overflow: this.props.overflow === true ? "scroll" : undefined,
- pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
+ overflow: this.props.overflow === true ? "scroll" : undefined, backgroundColor: "white",
+ pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative",
}} >
<div className="collectionSchemaView-tableContainer"
- style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }}
+ style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
onKeyPress={this.onKeyPress}
+ onContextMenu={this.onSpecificMenu}
onPointerDown={this.onPointerDown}
onWheel={e => this.props.active(true) && e.stopPropagation()}
onDrop={e => this.onExternalDrop(e, {})}
@@ -646,7 +618,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
</div>
{this.dividerDragger}
{!this.previewWidth() ? (null) : this.previewPanel}
- {this._headerOpen ? menu : null}
+ {this._headerOpen && this.props.active() ? menu : null}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index fe3d57bdb..fb0bce53e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -38,6 +38,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
_masonryGridRef: HTMLDivElement | null = null;
_draggerRef = React.createRef<HTMLDivElement>();
_pivotFieldDisposer?: IReactionDisposer;
+ _autoHeightDisposer?: IReactionDisposer;
_docXfs: any[] = [];
_columnStart: number = 0;
@observable _heightMap = new Map<string, number>();
@@ -47,7 +48,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
@computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
@computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); }
@computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
- @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, NumCast(this.layoutDoc._yMargin, 0)); } // 2 * this.gridGap)); }
+ @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, NumCast(this.layoutDoc._yMargin, 5)); } // 2 * this.gridGap)); }
@computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
@computed get isStackingView() { return BoolCast(this.layoutDoc._columnsStack, true); }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
@@ -76,6 +77,9 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
const dxf = () => this.getDocTransform(d, dref.current!);
this._docXfs.push({ dxf, width, height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
+ if (height() < 5) {
+ console.log("here" + height());
+ }
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} >
{this.getDisplayDoc(d, (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc, dxf, width)}
@@ -148,10 +152,12 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
() => this.pivotField,
() => this.layoutDoc._columnHeaders = new List()
);
+ this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, this.forceAutoHeight);
}
componentWillUnmount() {
super.componentWillUnmount();
this._pivotFieldDisposer?.();
+ this._autoHeightDisposer?.();
}
@action
@@ -193,7 +199,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
const height = () => this.getDocHeight(doc);
- const opacity = () => this.Document.currentFrame === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document.currentFrame))?.opacity;
+ const opacity = () => this.Document._currentFrame === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
return <ContentFittingDocumentView
Document={doc}
DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
@@ -217,6 +223,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
opacity={opacity}
focus={this.focusDocument}
docFilters={this.docFilters}
+ searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
@@ -249,8 +256,10 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
if (!(this.layoutDoc._columnsFill)) wid = Math.min(layoutDoc[WidthSym](), wid);
return wid * aspect;
}
- return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.max(20, layoutDoc[HeightSym]());
+ return layoutDoc._fitWidth ?
+ (!nh ? this.props.PanelHeight() - 2 * this.yMargin :
+ Math.min(wid * nh / (nw || 1), this.layoutDoc._autoHeight ? 100000 : this.props.PanelHeight() - 2 * this.yMargin)) :
+ Math.max(20, layoutDoc[HeightSym]());
}
columnDividerDown = (e: React.PointerEvent) => {
@@ -340,9 +349,11 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
- if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
- type = types[0];
+ if (this.pivotField) {
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
+ if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+ type = types[0];
+ }
}
const cols = () => this.isStackingView ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
@@ -354,16 +365,16 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
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.layoutDoc._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", "")))));
+ Doc.Layout(doc)._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
}
}));
this.observer.observe(ref);
}
}}
- key={heading ? heading.heading : ""}
+ key={heading?.heading ?? ""}
cols={cols}
headings={this.headings}
- heading={heading ? heading.heading : ""}
+ heading={heading?.heading ?? ""}
headingObject={heading}
docList={docList}
parent={this}
@@ -383,6 +394,11 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety);
}
+ forceAutoHeight = () => {
+ const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
+ Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
+ }
+
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
const key = this.pivotField;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
@@ -449,6 +465,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
const subItems: ContextMenuProps[] = [];
subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" });
subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+ subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
}
}
@@ -489,7 +506,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
transformOrigin: "top left",
}}
onScroll={action(e => {
- if (!this.props.isSelected() && this.props.renderDepth) e.currentTarget.scrollTop = this._scroll;
+ if (!this.props.isSelected(true) && this.props.renderDepth) e.currentTarget.scrollTop = this._scroll;
else this._scroll = e.currentTarget.scrollTop;
})}
onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index f193a9787..fd2ae03d8 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,35 +1,31 @@
import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../fields/Doc";
import { RichTextField } from "../../../fields/RichTextField";
+import { listSpec } from "../../../fields/Schema";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { ScriptField } from "../../../fields/ScriptField";
-import { NumCast, StrCast, Cast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { ImageField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, setupMoveUpEvents } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager } from "../../util/DragManager";
+import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
-import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
import "./CollectionStackingView.scss";
-import { listSpec } from "../../../fields/Schema";
-import { SnappingManager } from "../../util/SnappingManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-library.add(faPalette);
-
interface CSVFieldColumnProps {
cols: () => number;
headings: () => object[];
@@ -136,7 +132,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
addDocument = (value: string, shiftDown?: boolean) => {
if (!value) return false;
const key = StrCast(this.props.parent.props.Document._pivotField);
- const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true });
+ const newDoc = Docs.Create.TextDocument(value, { _height: 18, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, title: value, _autoHeight: true });
newDoc[key] = this.getValue(this.props.heading);
const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
@@ -269,7 +265,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
Doc.GetProto(this.props.parent.props.Document)[name] = "";
- const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+ const created = Docs.Create.TextDocument("", { title: name, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 250, _autoHeight: true });
if (created) {
if (this.props.parent.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document);
@@ -289,9 +285,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const heading = this._heading;
const style = this.props.parent;
const singleColumn = style.isStackingView;
- const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.props.Document._yMargin);
+ const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.props.Document._yMargin, 5);
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
- const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
+ const evContents = heading ? heading : this.props?.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
const headerEditableViewProps = {
GetValue: () => evContents,
SetValue: this.headingChanged,
@@ -310,7 +306,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const headingView = this.props.headingObject ?
<div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
style={{
- marginTop: NumCast(this.props.parent.props.Document._yMargin),
+ marginTop: NumCast(this.props.parent.props.Document._yMargin, 5),
width: (style.columnWidth) /
((uniqueHeadings.length +
((this.props.parent.props.Document._chromeStatus !== 'view-mode' && this.props.parent.props.Document._chromeStatus !== 'disabled') ? 1 : 0)) || 1)
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3f2ad47a5..8f0710f4b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, reaction, observable, runInAction
import { basename } from 'path';
import CursorField from "../../../fields/CursorField";
import { Doc, Opt, Field, DocListCast } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
+import { Id, ToString } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
@@ -111,6 +111,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
return this.props.ignoreFields?.includes("_docFilters") ? [] :
[...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
}
+ searchFilterDocs = () => {
+ return [...this.props.searchFilterDocs(), ...DocListCast(this.props.Document._searchFilterDocs)];
+ }
@computed get childDocs() {
let rawdocs: (Doc | Promise<Doc>)[] = [];
if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
@@ -128,52 +131,43 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
- let searchDocs = DocListCast(this.props.Document._searchDocs);
-
+ const docFilters = this.docFilters();
+ let searchDocs = this.searchFilterDocs();
+ if (this.props.Document.dontRegisterView || (!docFilters.length && !searchDocs.length)) return childDocs;
- let docsforFilter: Doc[] = childDocs;
-
- if (searchDocs !== undefined && searchDocs.length > 0) {
- docsforFilter = [];
- const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- console.log(searchDocs);
- searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript);
- console.log(this.docFilters());
- console.log(searchDocs);
- childDocs.forEach((d) => {
- if (d.data !== undefined) {
- let newdocs = DocListCast(d.data);
- if (newdocs.length > 0) {
- let displaycheck: boolean | undefined = undefined;
- let newarray: Doc[] = [];
- while (newdocs.length > 0) {
- newarray = [];
- newdocs.forEach((t) => {
- if (d.data !== undefined) {
- const newdocs = DocListCast(t.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- if (searchDocs.includes(t)) {
- displaycheck = true;
- }
- });
- newdocs = newarray;
- }
- if (displaycheck === true) {
- docsforFilter.push(d);
- }
+ const docsforFilter: Doc[] = [];
+ const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+ childDocs.forEach((d) => {
+ if (this.props.Document.title === "lose this") {
+ console.log('here"')
+ }
+ if (d.title === "lose this") {
+ console.log('here"')
+ }
+ let notFiltered = d.z || ((!searchDocs.length || searchDocs.includes(d)) && (!docFilters.length || DocUtils.FilterDocs([d], docFilters, docRangeFilters, viewSpecScript).length > 0));
+ const fieldKey = Doc.LayoutFieldKey(d);
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
+ const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+ if (data !== undefined) {
+ let subDocs = DocListCast(data);
+ if (subDocs.length > 0) {
+ let newarray: Doc[] = [];
+ notFiltered = notFiltered || (!searchDocs.length && docFilters.length && DocUtils.FilterDocs(subDocs, docFilters, docRangeFilters, viewSpecScript).length);
+ while (subDocs.length > 0 && !notFiltered) {
+ newarray = [];
+ subDocs.forEach((t) => {
+ const fieldKey = Doc.LayoutFieldKey(t);
+ const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView");
+ notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && (!docFilters.length || DocUtils.FilterDocs([t], docFilters, docRangeFilters, viewSpecScript).length));
+ DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc));
+ });
+ subDocs = newarray;
}
}
- if (searchDocs.includes(d)) {
- docsforFilter.push(d);
- }
- });
- return docsforFilter;
- }
- const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- return this.props.Document.dontRegisterView ? childDocs : DocUtils.FilterDocs(childDocs, this.docFilters(), docRangeFilters, viewSpecScript);
+ }
+ notFiltered && docsforFilter.push(d);
+ });
+ return docsforFilter;
}
@action
@@ -240,13 +234,14 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse);
} else added = res;
- !added && alert("You don't have permission to perform this move");
- e.stopPropagation();
+ added && e.stopPropagation();
+ return added;
} else {
ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
added = this.addDocument(docDragData.droppedDocuments);
}
- added && e.stopPropagation();
+ !added && alert("You cannot perform this move");
+ e.stopPropagation();
return added;
}
else if (de.complete.annoDragData) {
@@ -296,7 +291,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
}
} else if (text) {
- this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
+ this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 100, _height: 25 }));
}
return;
}
@@ -408,7 +403,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
_height: 315,
_nativeWidth: 850,
_nativeHeight: 962,
- UseCors: true
+ useCors: true
});
newDoc.data = new WebField(uriList.split("#annotations:")[0]); // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
this.addDocument(newDoc);
@@ -472,7 +467,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
completed?.();
} else {
if (text && !text.includes("https://")) {
- UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop");
+ UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop");
}
}
disposer();
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index c9bf82406..f96a5c4f0 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -85,6 +85,15 @@
white-space: pre-wrap;
min-width: 10px;
}
+.docContainer-system {
+ font-variant: all-small-caps;
+ border-radius: 5px;
+ background: grey;
+ color: white;
+ padding-left: 3px;
+ padding-right: 3px;
+ padding-bottom: 2px;
+}
.treeViewItem-openRight {
display: none;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d096e7d66..f13fee776 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,17 +1,16 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym, DocListCastOrNull } 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, returnEmptyFilter } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils, returnEmptyFilter, returnEmptyDoclist } 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 } from "../../util/DragManager";
import { Scripting } from '../../util/Scripting';
@@ -21,7 +20,6 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from "../EditableView";
-import { MainView } from '../MainView';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import { DocumentView } from '../nodes/DocumentView';
import { ImageBox } from '../nodes/ImageBox';
@@ -33,15 +31,16 @@ import { CollectionViewType } from './CollectionView';
import React = require("react");
import { makeTemplate } from '../../util/DropConverter';
import { TraceMobx } from '../../../fields/util';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { CollectionDockingView } from './CollectionDockingView';
export interface TreeViewProps {
document: Doc;
dataDoc?: Doc;
- libraryPath: Doc[] | undefined;
containingCollection: Doc;
prevSibling?: Doc;
renderDepth: number;
- deleteDoc: (doc: Doc | Doc[]) => boolean;
+ removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined;
moveDocument: DragManager.MoveFunction;
dropAction: dropActionType;
addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
@@ -55,7 +54,7 @@ export interface TreeViewProps {
ScreenToLocalTransform: () => Transform;
backgroundColor?: (doc: Doc, renderDepth: number) => string | undefined;
outerXf: () => { translateX: number, translateY: number };
- treeViewDoc: Doc;
+ treeView: CollectionTreeView;
parentKey: string;
active: (outsideReaction?: boolean) => boolean;
treeViewHideHeaderFields: () => boolean;
@@ -64,6 +63,8 @@ export interface TreeViewProps {
onCheckedClick?: () => ScriptField;
onChildClick?: () => ScriptField;
ignoreFields?: string[];
+ firstLevel: boolean;
+ whenActiveChanged: (isActive: boolean) => void;
}
@observer
@@ -77,6 +78,7 @@ export interface TreeViewProps {
*/
class TreeView extends React.Component<TreeViewProps> {
private _editTitleScript: (() => ScriptField) | undefined;
+ private _openScript: (() => ScriptField) | undefined;
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
@@ -88,23 +90,25 @@ class TreeView extends React.Component<TreeViewProps> {
get doc() { return this.props.document; }
get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); }
get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive
- get defaultExpandedView() { return this.childDocs.length ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); }
+ get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; }
+ get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode ? "layout" : "fields"); }
+ get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); }
@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.doc.treeViewOpen = this._overrideTreeViewOpen = c;
}
@computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; }
- @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.defaultExpandedView); }
+ @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
@computed get dataDoc() { return this.doc[DataSym]; }
@computed get layoutDoc() { return Doc.Layout(this.doc); }
@computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; }
childDocList(field: string) {
const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined;
- 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 ? DocListCast(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
- DocListCast(this.doc[field])); // otherwise use the document's data field
+ return ((this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
+ (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
+ DocListCastOrNull(this.doc[field])); // otherwise use the document's data field
}
@computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
@@ -114,22 +118,25 @@ class TreeView extends React.Component<TreeViewProps> {
Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
}
- @undoBatch openRight = () => this.props.addDocTab(this.doc, "onRight", this.props.libraryPath);
+ @undoBatch openRight = () => this.props.addDocTab(this.doc, "add:right");
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- return this.doc !== target && this.props.deleteDoc(doc) && addDoc(doc);
+ return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc);
}
@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 = (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);
+ flg && Doc.RemoveDocFromList(this.doc, Doc.LayoutFieldKey(this.doc), doc), true);
}
constructor(props: any) {
super(props);
- const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" });
- this._editTitleScript = script && (() => script);
+ const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" });
+ const openScript = ScriptField.MakeScript(`openOnRight(self)`);
+ const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`);
+ this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript);
+ this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined;
if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false);
}
@@ -193,13 +200,13 @@ class TreeView extends React.Component<TreeViewProps> {
}}
OnTab={undoBatch((shift?: boolean) => {
shift ? this.props.outdentDocument?.() : this.props.indentDocument?.();
- setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", "*", false), 0);
+ setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", `${this.props.treeView._uniqueId}`, false), 0);
})}
/>)
preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
- dragData && (dragData.dropAction = this.props.treeViewDoc === dragData.treeViewDoc ? "same" : dragData.dropAction);
+ dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction);
}
@undoBatch
@@ -274,11 +281,11 @@ class TreeView extends React.Component<TreeViewProps> {
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.treeViewDoc, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
+ contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents),
+ this.props.treeView, 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,
this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
- [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields);
+ [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged);
} else {
contentElement = <EditableView key="editableView"
contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
@@ -320,16 +327,16 @@ class TreeView extends React.Component<TreeViewProps> {
(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 : expandKey === "annotations" ? this.childAnnos : this.childDocs;
const sortKey = `${this.fieldKey}-sortAscending`;
- return <ul key={expandKey + "more"} onClick={(e) => {
+ return <ul key={expandKey + "more"} className={this.doc.treeViewHideTitle ? "no-indent" : ""} onClick={(e) => {
this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true));
e.stopPropagation();
}}>
{!docs ? (null) :
- TreeView.GetChildElements(docs, this.props.treeViewDoc, this.layoutDoc,
+ TreeView.GetChildElements(docs, this.props.treeView, this.layoutDoc,
this.dataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
StrCast(this.doc.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.doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)}
+ [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul key={this.doc[Id] + this.doc.title}><div ref={this._dref} style={{ display: "inline-block" }} >
@@ -357,6 +364,7 @@ class TreeView extends React.Component<TreeViewProps> {
focus={returnFalse}
ScreenToLocalTransform={this.docTransform}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionDoc={this.props.containingCollection}
ContainingCollectionView={undefined}
addDocument={returnFalse}
@@ -383,7 +391,7 @@ class TreeView extends React.Component<TreeViewProps> {
this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
heading: this.props.containingCollection.title,
checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check",
- containingTreeView: this.props.treeViewDoc,
+ containingTreeView: this.props.treeView.props.Document,
}, console.log);
} else {
this.treeViewOpen = !this.treeViewOpen;
@@ -405,11 +413,13 @@ class TreeView extends React.Component<TreeViewProps> {
showContextMenu = (e: React.MouseEvent) => {
this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
}
- focusOnDoc = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(doc)?.props.focus(doc, true);
- contextMenuItems = () => [{ script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }];
- truncateTitleWidth = () => NumCast(this.props.treeViewDoc.treeViewTruncateTitleWidth, 0);
- showTitleEdit = () => ["*", this._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
- onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.editTitleScript));
+ contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }];
+ truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, 0);
+ @computed get showTitleEdit() {
+ return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
+ }
+ onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
+ onChildDoubleClick = () => (!this.props.treeView.props.Document.treeViewOutlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -418,36 +428,38 @@ class TreeView extends React.Component<TreeViewProps> {
TraceMobx();
const headerElements = this.props.treeViewHideHeaderFields() ? (null) :
<>
- <FontAwesomeIcon icon="cog" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />
+ <FontAwesomeIcon key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
if (this.treeViewOpen) {
- this.doc.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
- this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" :
- this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" :
- (this.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" :
- this.childDocs.length ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields");
+ this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView :
+ this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
+ this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" :
+ this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" :
+ (this.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" :
+ this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields");
}
this.treeViewOpen = true;
})}>
{this.treeViewExpandedView}
</span>
</>;
- const view = this.showTitleEdit() ? this.editableView("title") :
+ const view = this.showTitleEdit ? this.editableView("title") :
<DocumentView
ref={this._docRef}
Document={this.doc}
DataDoc={undefined}
- treeViewDoc={this.props.treeViewDoc}
- LibraryPath={this.props.libraryPath || emptyPath}
+ treeViewDoc={this.props.treeView.props.Document}
+ LibraryPath={emptyPath}
addDocument={undefined}
addDocTab={this.props.addDocTab}
rootSelected={returnTrue}
pinToPres={emptyFunction}
onClick={this.onChildClick}
+ onDoubleClick={this.onChildDoubleClick}
dropAction={this.props.dropAction}
moveDocument={this.move}
- removeDocument={this.removeDoc}
+ removeDocument={this.props.removeDoc}
ScreenToLocalTransform={this.getTransform}
ContentScaling={returnOne}
PanelWidth={this.truncateTitleWidth}
@@ -459,34 +471,32 @@ class TreeView extends React.Component<TreeViewProps> {
renderDepth={1}
focus={returnTrue}
parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
+ whenActiveChanged={this.props.whenActiveChanged}
bringToFront={emptyFunction}
- dontRegisterView={BoolCast(this.props.treeViewDoc.dontRegisterChildViews)}
+ dontRegisterView={BoolCast(this.props.treeView.props.Document.dontRegisterChildViews)}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={this.props.containingCollection}
/>;
return <>
- <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
+ <div className={`docContainer${Doc.IsSystem(this.props.document) ? "-system" : ""}`} ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
style={{
- fontWeight: this.doc.searchMatch ? "bold" : undefined,
+ fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined,
textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
- outline: BoolCast(this.doc.workspaceBrush) ? "dashed 1px #06123232" : undefined,
- pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none"
+ outline: this.doc === CurrentUserUtils.ActiveDashboard ? "dashed 1px #06123232" : undefined,
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
}} >
{view}
</div >
- {headerElements}
- <div className="treeViewItem-openRight" onClick={this.openRight}>
- <FontAwesomeIcon title="open in a new pane" icon="external-link-alt" size="sm" />
- </div>
+ {Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode ? (null) : headerElements}
</>;
}
render() {
TraceMobx();
const sorting = this.doc[`${this.fieldKey}-sortAscending`];
- if (this.showTitleEdit()) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll
+ if (this.showTitleEdit) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll
let par: any = this._header?.current;
if (par) {
while (par && par.className !== "collectionTreeView-dropTarget") par = par.parentNode;
@@ -497,34 +507,35 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
} else this._editMaxWidth = "";
- return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}>
- <li className="collection-child">
- <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => {
- if (this.props.active(true)) {
- e.stopPropagation();
- e.preventDefault();
- SelectionManager.DeselectAll();
- }
- }}
- onPointerDown={e => {
+ return this.doc.treeViewHideTitle && this.props.firstLevel ? this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent :
+ <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}>
+ <li className="collection-child">
+ <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => {
if (this.props.active(true)) {
e.stopPropagation();
e.preventDefault();
+ SelectionManager.DeselectAll();
}
}}
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- {this.renderBullet}
- {this.renderTitle}
- </div>
- <div className="treeViewItem-border" style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}>
- {!this.treeViewOpen || this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent}
- </div>
- </li>
- </div>;
+ onPointerDown={e => {
+ if (this.props.active(true)) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ {this.renderBullet}
+ {this.renderTitle}
+ </div>
+ <div className="treeViewItem-border" style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}>
+ {!this.treeViewOpen || this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent}
+ </div>
+ </li>
+ </div>;
}
public static GetChildElements(
childDocs: Doc[],
- treeViewDoc: Doc,
+ treeView: CollectionTreeView,
containingCollection: Doc,
dataDoc: Doc | undefined,
key: string,
@@ -546,10 +557,11 @@ class TreeView extends React.Component<TreeViewProps> {
treeViewHideHeaderFields: () => boolean,
treeViewPreventOpen: boolean,
renderedIds: string[],
- libraryPath: Doc[] | undefined,
onCheckedClick: undefined | (() => ScriptField),
onChildClick: undefined | (() => ScriptField),
- ignoreFields: string[] | undefined
+ ignoreFields: string[] | undefined,
+ firstLevel: boolean,
+ whenActiveChanged: (isActive: boolean) => void
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
@@ -601,7 +613,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
const indent = i === 0 ? undefined : () => {
- if (StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) {
+ if (remove && StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) {
const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1];
const fieldKey = fieldKeysub.split("\'")[1];
if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
@@ -612,7 +624,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
};
const outdent = !parentCollectionDoc ? undefined : () => {
- if (StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) {
+ if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) {
const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1];
const fieldKey = fieldKeysub.split("\'")[1];
Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false);
@@ -631,17 +643,16 @@ class TreeView extends React.Component<TreeViewProps> {
return !(child instanceof Doc) ? (null) : <TreeView
document={pair.layout}
dataDoc={pair.data}
- libraryPath={libraryPath ? [...libraryPath, containingCollection] : undefined}
containingCollection={containingCollection}
prevSibling={docs[i]}
- treeViewDoc={treeViewDoc}
+ treeView={treeView}
key={child[Id]}
indentDocument={indent}
outdentDocument={outdent}
onCheckedClick={onCheckedClick}
onChildClick={onChildClick}
renderDepth={renderDepth}
- deleteDoc={remove}
+ removeDoc={StrCast(containingCollection.freezeChildren).includes("remove") ? undefined : remove}
addDocument={addDocument}
backgroundColor={backgroundColor}
panelWidth={rowWidth}
@@ -658,7 +669,9 @@ class TreeView extends React.Component<TreeViewProps> {
treeViewHideHeaderFields={treeViewHideHeaderFields}
treeViewPreventOpen={treeViewPreventOpen}
renderedIds={renderedIds}
- ignoreFields={ignoreFields} />;
+ ignoreFields={ignoreFields}
+ firstLevel={firstLevel}
+ whenActiveChanged={whenActiveChanged} />;
});
}
}
@@ -675,6 +688,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
private treedropDisposer?: DragManager.DragDropDisposer;
private _mainEle?: HTMLDivElement;
+ public _uniqueId = Utils.GenerateGuid();
@computed get doc() { return this.props.Document; }
@computed get dataDoc() { return this.props.DataDoc || this.doc; }
@@ -725,14 +739,14 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myWorkspaces) {
- ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
- ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.doc), icon: "minus" });
+ if (!e.isPropagationStopped() && this.doc === CurrentUserUtils.MyDashboards) {
+ ContextMenu.Instance.addItem({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "plus" });
+ ContextMenu.Instance.addItem({ description: "Delete Dashboard", event: () => this.remove(this.doc), icon: "minus" });
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- } else if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myRecentlyClosed) {
- ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.UserDoc().myRecentlyClosed = new List<Doc>(), icon: "plus" });
+ } else if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myRecentlyClosedDocs) {
+ ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.UserDoc().myRecentlyClosedDocs = new List<Doc>(), icon: "plus" });
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
@@ -788,6 +802,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
onClicks.push({
description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
});
+ onClicks.push({
+ description: `${this.props.Document.treeViewOutlineMode ? "Delay " : "Immediate "} Title Editing`, event: () => UndoManager.RunInBatch(() => this.props.Document.treeViewOutlineMode = !this.props.Document.treeViewOutlineMode, "edit onCheckedClick"), icon: "edit"
+ });
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
@@ -805,6 +822,11 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
onChildClick = () => {
return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
}
+ _isChildActive = false;
+ whenActiveChanged = (isActive: boolean) => {
+ this.props.whenActiveChanged(this._isChildActive = isActive);
+ }
+ active = (outsideReaction: boolean | undefined) => this.props.active(outsideReaction) || this._isChildActive;
render() {
TraceMobx();
if (!(this.doc instanceof Doc)) return (null);
@@ -812,6 +834,13 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
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;
+ const childElements = childDocs && TreeView.GetChildElements(childDocs, this, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
+ this.outerXf, this.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
+ BoolCast(this.doc.treeViewPreventOpen), [], this.props.onCheckedClick,
+ this.onChildClick, this.props.ignoreFields, true, this.whenActiveChanged);
+ const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle;
+
return !childDocs ? (null) : (
<div className="collectionTreeView-container" onContextMenu={this.onContextMenu}>
<div className="collectionTreeView-dropTarget" id="body"
@@ -820,12 +849,12 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
paddingLeft: `${NumCast(this.doc._xPadding, 10)}px`,
paddingRight: `${NumCast(this.doc._xPadding, 10)}px`,
paddingTop: `${NumCast(this.doc._yPadding, 20)}px`,
- pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined,
}}
onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
- {this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : <EditableView
+ {hideTitle ? (null) : <EditableView
contents={this.dataDoc.title}
editing={false}
display={"block"}
@@ -840,42 +869,13 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
})} />}
{this.doc.allowClear ? this.renderClearButton : (null)}
- <ul className="no-indent" style={{ width: "max-content" }} >
- {
- TreeView.GetChildElements(childDocs, this.doc, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
- moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
- this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
- BoolCast(this.doc.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
- this.onChildClick, this.props.ignoreFields)
- }
- </ul>
+ <ul className="no-indent" style={{ width: "max-content" }} > {childElements} </ul>
</div >
</div>
);
}
}
-Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) {
- const allCollectionDocs = DocListCast(dataDoc[dataKey]);
- const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
- set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
-
- let nonNumbers = 0;
- facetValues.map(val => {
- const num = Number(val);
- if (Number.isNaN(num)) {
- nonNumbers++;
- }
- });
- const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
- const doc = new Doc();
- doc.title = facetValue.toString();
- doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue });
- return doc;
- });
- return new List<Doc>(facetValueDocSet);
-});
-
Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []);
for (let i = 0; i < docFilters.length; i += 3) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 6dd21ef7f..ba8e23447 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,6 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faEye } from '@fortawesome/free-regular-svg-icons';
-import { faColumns, faCopy, faEllipsisV, faFingerprint, faGlobeAmericas, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable } from 'mobx';
import { observer } from "mobx-react";
@@ -18,7 +15,7 @@ import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx, GetEffectiveAcl, SharingPermissions, distributeAcls } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyDoclist } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -53,8 +50,6 @@ export const Flyout = higflyout.default;
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
-library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faGlobeAmericas, faEllipsisV, faImage, faEye as any, faCopy);
-
export enum CollectionViewType {
Invalid = "invalid",
Freeform = "freeform",
@@ -69,7 +64,7 @@ export enum CollectionViewType {
Carousel = "carousel",
Carousel3D = "3D Carousel",
Linear = "linear",
- Staff = "staff",
+ //Staff = "staff",
Map = "map",
Grid = "grid",
Pile = "pileup"
@@ -139,6 +134,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
const docs = doc instanceof Doc ? [doc] : doc;
+
+
+ if (docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document))) return false;
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
const added = docs.filter(d => !docList.includes(d));
@@ -159,7 +157,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
if (effectiveAcl === AclAddonly) {
- added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
+ added.map(doc => {
+ Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
+ doc.context = this.props.Document;
+ });
}
else {
added.map(doc => {
@@ -177,14 +178,11 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
doc.displayTimecode = undefined;
}
+ doc._stayInCollection = undefined;
doc.context = this.props.Document;
});
- added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
- // targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
(targetDataDoc[this.props.fieldKey] as List<Doc>).push(...added);
targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- const lastModified = "lastModified";
- targetDataDoc[lastModified] = new DateField(new Date(Date.now()));
}
}
}
@@ -200,11 +198,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
const toRemove = value.filter(v => docs.includes(v));
if (toRemove.length !== 0) {
- const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
+ const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
toRemove.forEach(doc => {
Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc);
recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
- doc.deleted = true;
});
return true;
}
@@ -253,7 +250,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
- case CollectionViewType.Staff: return (<CollectionStaffView key="collview" {...props} />);
+ //case CollectionViewType.Staff: return (<CollectionStaffView key="collview" {...props} />);
case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />);
case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
@@ -274,7 +271,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return this.SubViewHelper(type, renderProps);
}
-
setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
const subItems: ContextMenuProps[] = [];
@@ -286,7 +282,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" });
subItems.push({ description: "Stacking (AutoHeight)", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" });
- subItems.push({ description: "Staff", event: () => func(CollectionViewType.Staff), icon: "music" });
subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" });
subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" });
subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" });
@@ -309,7 +304,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
this.setupViewTypes("UI Controls...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
- this.props.addDocTab(newRendition, "onRight");
+ this.props.addDocTab(newRendition, "add:right");
return newRendition;
}, false);
@@ -317,10 +312,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const optionItems = options && "subitems" in options ? options.subitems : [];
!Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }) : null;
if (this.props.Document.childLayout instanceof Doc) {
- optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
+ optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "add:right"), icon: "project-diagram" });
}
if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
- optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
+ optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
}
!Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
@@ -335,7 +330,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
const alias = Doc.MakeAlias(this.props.Document);
DocUtils.makeCustomViewClicked(alias, undefined, func.key);
- this.props.addDocTab(alias, "onRight");
+ this.props.addDocTab(alias, "add:right");
}
}));
DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
@@ -371,6 +366,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
}
+
get _facetWidth() { return NumCast(this.props.Document._facetWidth); }
set _facetWidth(value) { this.props.Document._facetWidth = value; }
@@ -394,9 +390,11 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout);
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
+
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
+
get childDocs() {
const dfield = this.dataField;
const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.rootDocument, Doc, null) ? [Cast(this.props.Document.rootDocument, Doc, null)] : []);
@@ -404,91 +402,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript);
return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
}
- @computed get _allFacets() {
- TraceMobx();
- const facets = new Set<string>(["type", "text", "data", "author", "ACL"]);
- this.childDocs.filter(child => child).forEach(child => child && Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
- Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key)));
- return Array.from(facets).filter(f => !f.startsWith("_") && !["proto", "zIndex", "isPrototype", "context", "text-noTemplate"].includes(f)).sort();
- }
-
- /**
- * Responds to clicking the check box in the flyout menu
- */
- facetClick = (facetHeader: string) => {
- const facetCollection = this.props.Document;
- const found = DocListCast(facetCollection[this.props.fieldKey + "-filter"]).findIndex(doc => doc.title === facetHeader);
- if (found !== -1) {
- (facetCollection[this.props.fieldKey + "-filter"] as List<Doc>).splice(found, 1);
- const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
- if (docFilter) {
- let index: number;
- while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
- docFilter.splice(index, 3);
- }
- }
- const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
- if (docRangeFilters) {
- let index: number;
- while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
- docRangeFilters.splice(index, 3);
- }
- }
- } else {
- const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
- var rtfields = 0;
- const facetValues = Array.from(allCollectionDocs.reduce((set, child) => {
- const field = child[facetHeader] as Field;
- const fieldStr = Field.toString(field);
- if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++;
- return set.add(fieldStr);
- }, new Set<string>()));
-
- let nonNumbers = 0;
- let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
- facetValues.map(val => {
- const num = Number(val);
- if (Number.isNaN(num)) {
- nonNumbers++;
- } else {
- minVal = Math.min(num, minVal);
- maxVal = Math.max(num, maxVal);
- }
- });
- let newFacet: Opt<Doc>;
- if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) {
- newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
- Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- newFacet.target = this.props.Document;
- newFacet._textBoxPadding = 4;
- const scriptText = `setDocFilter(this.target, "${facetHeader}", text, "match")`;
- newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
- } else if (nonNumbers / facetValues.length < .1) {
- newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true });
- const newFacetField = Doc.LayoutFieldKey(newFacet);
- const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
- Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05);
- const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05);
- newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
- newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
- Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal;
- Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
- newFacet.target = this.props.Document;
- const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
- newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
- Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
- } else {
- newFacet = new Doc();
- newFacet.title = facetHeader;
- newFacet.treeViewOpen = true;
- newFacet.type = DocumentType.COL;
- const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
- newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables);
- }
- newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
- }
- }
onPointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
@@ -497,77 +410,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0), false);
}
- filterBackground = () => "rgba(105, 105, 105, 0.432)";
- get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
- @computed get scriptField() {
- const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
- const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
- return script ? () => script : undefined;
- }
- @computed get filterView() {
- TraceMobx();
- const facetCollection = this.props.Document;
- const flyout = (
- <div className="collectionTimeView-flyout" style={{ width: `${this.facetWidth()}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
- {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
- <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey + "-filter"]).some(d => d.title === facet)} />
- <span className="checkmark" />
- {facet}
- </label>)}
- </div>
- );
-
- return !this._facetWidth || this.props.dontRegisterView ? (null) : <div className="collectionTimeView-treeView" style={{ width: `${this.facetWidth()}px`, overflow: this.facetWidth() < 15 ? "hidden" : undefined }}>
- <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}>
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
- <div className="collectionTimeView-button">
- <FontAwesomeIcon icon={faEdit} size={"lg"} />
- <span className="collectionTimeView-span">Facet Filters</span>
- </div>
- </Flyout>
- </div>
- <div className="collectionTimeView-tree" key="tree">
- <CollectionTreeView
- PanelPosition={""}
- Document={facetCollection}
- DataDoc={facetCollection}
- fieldKey={`${this.props.fieldKey}-filter`}
- CollectionView={this}
- docFilters={returnEmptyFilter}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- PanelWidth={this.facetWidth}
- PanelHeight={this.props.PanelHeight}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- LibraryPath={emptyPath}
- rootSelected={this.props.rootSelected}
- renderDepth={1}
- dropAction={this.props.dropAction}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- isSelected={returnFalse}
- select={returnFalse}
- bringToFront={emptyFunction}
- active={this.props.active}
- whenActiveChanged={returnFalse}
- treeViewHideTitle={true}
- ContentScaling={returnOne}
- focus={returnFalse}
- treeViewHideHeaderFields={true}
- onCheckedClick={this.scriptField}
- ignoreFields={this.ignoreFields}
- annotationsKey={""}
- dontRegisterView={true}
- backgroundColor={this.filterBackground}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- addDocument={returnFalse} />
- </div>
- </div>;
- }
-
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString);
@@ -584,10 +426,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
- const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
- `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
+ const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document._isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
+ `${CurrentUserUtils.ActiveDashboard?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
- style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}>
+ style={{ pointerEvents: this.props.Document._isBackground ? "none" : undefined, boxShadow }}>
{this.showIsTagged()}
<div className="collectionView-facetCont" style={{ display: this.props.PanelPosition === "absolute" ? "flex" : "", justifyContent: this.props.PanelPosition === "absolute" ? "center" : "", width: `calc(100% - ${this.facetWidth()}px)` }}>
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
@@ -598,11 +440,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
:
""))}
- {(Doc.UserDoc()?.noviceMode || !this.props.isSelected() && !this.props.Document.forceActive) || this.props.Document.hideFilterView ? (null) :
- <div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown}
- style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "60%" }} />
- }
- {Doc.UserDoc()?.noviceMode || this.facetWidth() < 10 ? (null) : this.filterView}
</div>);
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
deleted file mode 100644
index 149d4927b..000000000
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import * as React from "react";
-import './ParentDocumentSelector.scss';
-import { Doc } from "../../../fields/Doc";
-import { observer } from "mobx-react";
-import { observable, action, runInAction, trace, computed, reaction, IReactionDisposer } from "mobx";
-import { Id } from "../../../fields/FieldSymbols";
-import { SearchUtil } from "../../util/SearchUtil";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { NumCast, StrCast } from "../../../fields/Types";
-import { CollectionViewType } from "./CollectionView";
-import { DocumentButtonBar } from "../DocumentButtonBar";
-import { DocumentManager } from "../../util/DocumentManager";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faCog, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { DocumentView } from "../nodes/DocumentView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Tooltip } from "@material-ui/core";
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-library.add(faCog);
-
-type SelectorProps = {
- Document: Doc,
- Stack?: any,
- addDocTab(doc: Doc, location: string): void
-};
-
-@observer
-export class SelectorContextMenu extends React.Component<SelectorProps> {
- @observable private _docs: { col: Doc, target: Doc }[] = [];
- @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
- _reaction: IReactionDisposer | undefined;
-
- componentDidMount() {
- this._reaction = reaction(() => this.props.Document, () => this.fetchDocuments(), { fireImmediately: true });
- }
- componentWillUnmount() {
- this._reaction?.();
- }
- async fetchDocuments() {
- const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document));
- const containerProtoSets = await Promise.all(aliases.map(async alias =>
- ((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
- const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
- const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => {
- return (SearchUtil.GetAliasesOfDocument(container));
- }));
- const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
- const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => {
- return (SearchUtil.GetAliasesOfDocument(dp));
- }));
- const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
- runInAction(() => {
- this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
- this._otherDocs = [];
- });
- }
-
- getOnClick({ col, target }: { col: Doc, target: Doc }) {
- return () => {
- col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
- if (col._viewType === CollectionViewType.Freeform) {
- const newPanX = NumCast(target.x) + NumCast(target._width) / 2;
- const newPanY = NumCast(target.y) + NumCast(target._height) / 2;
- col._panX = newPanX;
- col._panY = newPanY;
- }
- this.props.addDocTab(col, "inTab"); // bcz: dataDoc?
- };
- }
-
- render() {
- return <div >
- <p key="contexts">Contexts:</p>
- {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
- {this._otherDocs.length ? <hr key="hr" /> : null}
- {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title?.toString()}</a></p>)}
- </div>;
- }
-}
-
-@observer
-export class ParentDocSelector extends React.Component<SelectorProps> {
- render() {
- const flyout = (
- <div className="parentDocumentSelector-flyout" title=" ">
- <SelectorContextMenu {...this.props} />
- </div>
- );
- return <div title="Show Contexts" onPointerDown={e => e.stopPropagation()} className="parentDocumentSelector-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
- <span className="parentDocumentSelector-button" >
- <FontAwesomeIcon icon={faChevronCircleUp} size={"lg"} />
- </span>
- </Flyout>
- </div>;
- }
-}
-
-@observer
-export class DockingViewButtonSelector extends React.Component<{ views: () => DocumentView[], Stack: any }> {
- customStylesheet(styles: any) {
- return {
- ...styles,
- panel: {
- ...styles.panel,
- minWidth: "100px"
- },
- };
- }
- _ref = React.createRef<HTMLDivElement>();
-
- @computed get flyout() {
- return (
- <div className="ParentDocumentSelector-flyout" title=" " ref={this._ref}>
- <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
- </div>
- );
- }
-
- render() {
- return <Tooltip title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
- <span onPointerDown={e => {
- if (getComputedStyle(this._ref.current!).width !== "100%") {
- e.stopPropagation(); e.preventDefault();
- }
- this.props.views()[0]?.select(false);
- }} className="buttonSelector">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
- <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
- </Flyout>
- </span>
- </Tooltip>;
- }
-}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index a2c529a21..1fb7aa04a 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -5,16 +5,15 @@ import { action, computed, observable } 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 "../../../fields/Doc";
+import { Doc, DocListCast, Field, Opt, AclPrivate, AclReadonly, DataSym } 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 } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, returnEmptyDoclist } from "../../../Utils";
import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
import { CompileScript, Transformer, ts } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -22,11 +21,15 @@ import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
import '../DocumentDecorations.scss';
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
+import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaBooleanCell } from "./CollectionSchemaCells";
import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
import "./CollectionSchemaView.scss";
import { CollectionView } from "./CollectionView";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { GetEffectiveAcl } from "../../../fields/util";
+import { DateField } from "../../../fields/DateField";
+import { ImageField } from "../../../fields/URLField";
enum ColumnType {
@@ -45,7 +48,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
["title", ColumnType.String],
["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number],
["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+ ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
]);
export interface SchemaTableProps {
@@ -70,11 +73,12 @@ export interface SchemaTableProps {
isSelected: (outsideReaction?: boolean) => boolean;
isFocused: (document: Doc, outsideReaction: boolean) => boolean;
setFocused: (document: Doc) => void;
- setPreviewDoc: (document: Doc) => void;
+ setPreviewDoc: (document: Opt<Doc>) => void;
columns: SchemaHeaderField[];
documentKeys: any[];
headerIsEditing: boolean;
openHeader: (column: any, screenx: number, screeny: number) => void;
+ onClick: (e: React.MouseEvent) => void;
onPointerDown: (e: React.PointerEvent) => void;
onResizedChange: (newResized: Resize[], event: any) => void;
setColumns: (columns: SchemaHeaderField[]) => void;
@@ -90,7 +94,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@observable _cellIsEditing: boolean = false;
@observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
- @observable _openCollections: Array<string> = [];
+ @observable _openCollections: Set<number> = new Set;
@observable _showDoc: Doc | undefined;
@observable _showDataDoc: any = "";
@@ -135,16 +139,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@action
changeSorting = (col: any) => {
- if (col.desc === undefined) {
- // no sorting
- this.props.changeColumnSort(col, true);
- } else if (col.desc === true) {
- // descending sort
- this.props.changeColumnSort(col, false);
- } else if (col.desc === false) {
- // ascending sort
- this.props.changeColumnSort(col, undefined);
- }
+ this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
}
@action
@@ -152,7 +147,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@computed get tableColumns(): Column<Doc>[] {
-
const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
const columns: Column<Doc>[] = [];
const tableIsFocused = this.props.isFocused(this.props.Document, false);
@@ -160,26 +154,16 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const focusedCol = this._focusedCell.col;
const isEditable = !this.props.headerIsEditing;
- if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) {
- columns.push(
- {
- expander: true,
- Header: "",
- width: 30,
- Expander: (rowInfo) => {
- if (rowInfo.original.type === "collection") {
- if (rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-up"} size="sm" /></div>;
- if (!rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-down"} size="sm" /></div>;
- } else {
- return null;
- }
- }
- }
- );
- }
- this.props.active;
-
- const cols = this.props.columns.map(col => {
+ columns.push({
+ expander: true, Header: "", width: 58,
+ Expander: (rowInfo) => {
+ return rowInfo.original.type !== DocumentType.COL ? (null) :
+ <div className="collectionSchemaView-expander" onClick={action(() => (this._openCollections[rowInfo.isExpanded ? "delete" : "add"])(rowInfo.viewIndex))}>
+ <FontAwesomeIcon icon={rowInfo.isExpanded ? "caret-down" : "caret-right"} size="lg" />
+ </div>;
+ }
+ });
+ columns.push(...this.props.columns.map(col => {
const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" :
this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" :
this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" :
@@ -207,29 +191,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
width={"100%"}
/>;
-
-
const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
-
- const header =
- <div //className="collectionSchemaView-header"
- //onClick={e => this.props.openHeader(col, menuContent, e.clientX, e.clientY)}
- className="collectionSchemaView-menuOptions-wrapper"
- style={{
- background: col.color, padding: "2px",
- display: "flex", cursor: "default", height: "100%",
- }}>
- {/* <FontAwesomeIcon onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> */}
- {keysDropdown}
- <div onClick={e => this.changeSorting(col)}
- style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}>
- <FontAwesomeIcon icon={sortIcon} size="lg" />
- </div>
- </div>;
+ const header = <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}>
+ {keysDropdown}
+ <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}>
+ <FontAwesomeIcon icon={sortIcon} size="lg" />
+ </div>
+ </div>;
return {
Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
- accessor: (doc: Doc) => doc ? doc[col.heading] : 0,
+ accessor: (doc: Doc) => doc ? Field.toString(doc[col.heading] as Field) : 0,
id: col.heading,
Cell: (rowProps: CellInfo) => {
const rowIndex = rowProps.index;
@@ -258,21 +230,22 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
showDoc: this.showDoc,
};
- const colType = this.getColumnType(col);
- if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props} />;
- if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props} />;
- if (colType === ColumnType.Boolean) return <CollectionSchemaCheckboxCell {...props} />;
- if (colType === ColumnType.Doc) return <CollectionSchemaDocCell {...props} />;
- if (colType === ColumnType.Image) return <CollectionSchemaImageCell {...props} />;
- if (colType === ColumnType.List) return <CollectionSchemaListCell {...props} />;
- if (colType === ColumnType.Date) return <CollectionSchemaDateCell {...props} />;
- return <CollectionSchemaCell {...props} />;
+
+ switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
+ case ColumnType.Number: return <CollectionSchemaNumberCell {...props} />;
+ case ColumnType.String: return <CollectionSchemaStringCell {...props} />;
+ case ColumnType.Boolean: return <CollectionSchemaCheckboxCell {...props} />;
+ case ColumnType.Doc: return <CollectionSchemaDocCell {...props} />;
+ case ColumnType.Image: return <CollectionSchemaImageCell {...props} />;
+ case ColumnType.List: return <CollectionSchemaListCell {...props} />;
+ case ColumnType.Date: return <CollectionSchemaDateCell {...props} />;
+ default:
+ return <CollectionSchemaCell {...props} />;
+ }
},
minWidth: 200,
};
- });
- columns.push(...cols);
-
+ }));
columns.push({
Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
accessor: (doc: Doc) => 0,
@@ -281,8 +254,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const rowIndex = rowProps.index;
const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
- const props: CellProps = {
- row: rowIndex,
+ return <CollectionSchemaButtons {...{
+ row: rowProps.index,
col: columnIndex,
rowProps: rowProps,
isFocused: isFocused,
@@ -301,9 +274,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
setComputed: this.setComputed,
getField: this.getField,
showDoc: this.showDoc,
- };
-
- return <CollectionSchemaButtons {...props} />;
+ }} />;
},
width: 28,
resizable: false
@@ -314,14 +285,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
constructor(props: SchemaTableProps) {
super(props);
- // convert old schema columns (list of strings) into new schema columns (list of schema header fields)
- const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
- if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
- const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
- } else if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb"),
- new SchemaHeaderField("text", "#f1efeb"), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb")]);
+ if (this.props.Document._schemaHeaders === undefined) {
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date),
+ new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]);
}
}
@@ -334,7 +300,15 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
- return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+ const tableDoc = this.props.Document[DataSym];
+ const effectiveAcl = GetEffectiveAcl(tableDoc);
+
+ if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) {
+ doc.context = this.props.Document;
+ tableDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+ }
+ return false;
}
private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
@@ -360,19 +334,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
// TODO: editing border doesn't work :(
return {
- style: {
- border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb"
- }
+ style: { border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb" }
};
}
- @action
- onCloseCollection = (collection: Doc): void => {
- const index = this._openCollections.findIndex(col => col === collection[Id]);
- if (index > -1) this._openCollections.splice(index, 1);
- }
-
- @action onExpandCollection = (collection: Doc) => this._openCollections.push(collection[Id]);
@action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
@action
@@ -383,6 +348,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
pdoc && this.props.setPreviewDoc(pdoc);
+ e.stopPropagation();
+ } else if (e.keyCode === 27) {
+ this.props.setPreviewDoc(undefined);
+ e.stopPropagation(); // stopPropagation for left/right arrows
}
}
@@ -406,9 +375,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@undoBatch
- createRow = () => {
- this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
- }
+ createRow = action(() => {
+ this.props.addDocument(Docs.Create.TextDocument("", { title: "", _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 100, _height: 30 }));
+ this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
+ });
@undoBatch
@action
@@ -423,22 +393,21 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@action
- getColumnType = (column: SchemaHeaderField): ColumnType => {
- // added functionality to convert old column type stuff to new column type stuff -syip
+ getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => {
+ if (doc && field && column.type === ColumnType.Any) {
+ const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)];
+ if (val instanceof ImageField) return ColumnType.Image;
+ if (val instanceof Doc) return ColumnType.Doc;
+ if (val instanceof DateField) return ColumnType.Date;
+ if (val instanceof List) return ColumnType.List;
+ }
if (column.type && column.type !== 0) {
return column.type;
}
if (columnTypes.get(column.heading)) {
- column.type = columnTypes.get(column.heading)!;
- return columnTypes.get(column.heading)!;
- }
- const typesDoc = FieldValue(Cast(this.props.Document.schemaColumnTypes, Doc));
- if (!typesDoc) {
- column.type = ColumnType.Any;
- return ColumnType.Any;
+ return column.type = columnTypes.get(column.heading)!;
}
- column.type = NumCast(typesDoc[column.heading]);
- return NumCast(typesDoc[column.heading]);
+ return column.type = ColumnType.Any;
}
@undoBatch
@@ -467,11 +436,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@computed
get reactTable() {
const children = this.childDocs;
- const hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false);
- const expandedRowsList = this._openCollections.map(col => children.findIndex(doc => doc[Id] === col).toString());
- const expanded = {};
- //@ts-ignore
- expandedRowsList.forEach(row => expanded[row] = true);
+ const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false);
+ const expanded: { [name: string]: any } = {};
+ Array.from(this._openCollections.keys()).map(col => expanded[col.toString()] = true);
const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
return <ReactTable
@@ -489,17 +456,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
expanded={expanded}
resized={this.resized}
onResizedChange={this.props.onResizedChange}
- SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
+ SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== DocumentType.COL) ? (null) :
<div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
/>;
}
onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
- ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
- }
+ ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
}
getField = (row: number, col?: number) => {
@@ -560,10 +524,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
script =
`const $ = (row:number, col?:number) => {
- if(col === undefined) {
- return (doc as any)[key][row + ${row}];
- }
- return (doc as any)[key][row + ${row}][(doc as any)._schemaHeaders[col + ${col}].heading];
+ const rval = (doc as any)[key][row + ${row}];
+ return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading];
}
return ${script}`;
const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
@@ -583,9 +545,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
onOpenClick = () => {
- if (this._showDoc) {
- this.props.addDocTab(this._showDoc, "onRight");
- }
+ this._showDoc && this.props.addDocTab(this._showDoc, "add:right");
}
getPreviewTransform = (): Transform => {
@@ -594,13 +554,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
render() {
const preview = "";
- return <div className="collectionSchemaView-table" onPointerDown={this.props.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()}
+ return <div className="collectionSchemaView-table"
+ onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()}
onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
{this.reactTable}
- {StrCast(this.props.Document.type) !== "search" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
+ {StrCast(this.props.Document._chromeStatus) !== "disabled" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
: undefined}
{!this._showDoc ? (null) :
- <div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }}
+ <div className="collectionSchemaView-documentPreview"
style={{
position: "absolute", width: 150, height: 150,
background: "dimGray", display: "block", top: 0, left: 0,
@@ -621,6 +582,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
PanelHeight={() => 150}
ScreenToLocalTransform={this.getPreviewTransform}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
new file mode 100644
index 000000000..fdb801e03
--- /dev/null
+++ b/src/client/views/collections/TabDocView.scss
@@ -0,0 +1,22 @@
+input.lm_title:focus {
+ max-width: max-content !important;
+}
+.miniMap {
+ position: absolute;
+ overflow: hidden;
+ right: 10;
+ bottom: 10;
+ border: solid 1px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+
+ .miniOverlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+
+ .miniThumb {
+ background: #25252525;
+ position: absolute;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
new file mode 100644
index 000000000..fb4f5c366
--- /dev/null
+++ b/src/client/views/collections/TabDocView.tsx
@@ -0,0 +1,383 @@
+import 'golden-layout/src/css/goldenlayout-base.css';
+import 'golden-layout/src/css/goldenlayout-dark-theme.css';
+import { clamp } from 'lodash';
+import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import * as ReactDOM from 'react-dom';
+import { DataSym, Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Id } from '../../../fields/FieldSymbols';
+import { FieldId } from "../../../fields/RefField";
+import { listSpec } from '../../../fields/Schema';
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager, dropActionType } from "../../util/DragManager";
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { DocumentView } from "../nodes/DocumentView";
+import { PresBox } from '../nodes/PresBox';
+import { CollectionDockingView } from './CollectionDockingView';
+import "./TabDocView.scss";
+import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionViewType } from './CollectionView';
+import React = require("react");
+const _global = (window /* browser */ || global /* node */) as any;
+
+interface TabDocViewProps {
+ documentId: FieldId;
+ glContainer: any;
+}
+@observer
+export class TabDocView extends React.Component<TabDocViewProps> {
+ _mainCont: HTMLDivElement | null = null;
+ _tabReaction: IReactionDisposer | undefined;
+ @observable private _panelWidth = 0;
+ @observable private _panelHeight = 0;
+ @observable private _isActive: boolean = false;
+ @observable private _document: Doc | undefined;
+ @observable private _view: DocumentView | undefined;
+
+ get stack(): any { return (this.props as any).glContainer.parent.parent; }
+ get tab() { return (this.props as any).glContainer.tab; }
+ get view() { return this._view; }
+
+ @action
+ init = (tab: any, doc: Opt<Doc>) => {
+ if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+ tab._disposers = {} as { [name: string]: IReactionDisposer };
+ tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
+ tab.DashDoc = doc;
+ CollectionDockingView.Instance.tabMap.add(tab);
+
+ // setup the title element and set its size according to the # of chars in the title. Show the full title when clicked.
+ const titleEle = tab.titleElement[0];
+ titleEle.size = StrCast(doc.title).length + 3;
+ titleEle.value = doc.title;
+ titleEle.style["max-width"] = "100px";
+ titleEle.onchange = (e: any) => {
+ titleEle.size = e.currentTarget.value.length + 3;
+ Doc.GetProto(doc).title = e.currentTarget.value;
+ };
+ // shifts the focus to this tab when another tab is dragged over it
+ tab.element[0].onmouseenter = (e: MouseEvent) => {
+ if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
+ tab.header.parent.setActiveContentItem(tab.contentItem);
+ console.log("Seetting " + titleEle.value);
+ tab.setActive(true);
+ }
+ };
+ const onPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, (e) => {
+ !e.defaultPrevented && DragManager.StartDocumentDrag([dragHdl], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY);
+ return !e.defaultPrevented;
+ }, returnFalse, emptyFunction);
+ };
+
+ // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
+ tab.element[0].onclick = (e: any) => {
+ if (e.target.className !== "lm_close_tab" && this.view) {
+ SelectionManager.SelectDoc(this.view, false);
+ if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
+ titleEle.lastClick = Date.now();
+ (document.activeElement !== titleEle) && titleEle.focus();
+ }
+ };
+ tab._disposers.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments().some(v => v.topMost && v.props.Document === doc),
+ (selected) => selected && tab.contentItem !== tab.header.parent.getActiveContentItem() &&
+ UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), "tab switch"));
+
+ //attach the selection doc buttons menu to the drag handle
+ const stack = tab.contentItem.parent;
+ const dragHdl = document.createElement("span");
+ dragHdl.className = "collectionDockingView-gear";
+ dragHdl.style.position = "relative";
+ dragHdl.style.paddingLeft = "0px";
+ dragHdl.style.paddingRight = "12px";
+ tab._disposers.buttonDisposer = reaction(() => this.view, (view) => view &&
+ [ReactDOM.render(
+ <span title="Drag as document" className="collectionDockingView-drag" onPointerDown={onPointerDown} >
+ <CollectionDockingViewMenu views={() => [view]} Stack={stack} />
+ </span>, dragHdl),
+ tab._disposers.buttonDisposer?.()],
+ { fireImmediately: true });
+ tab.reactComponents = [dragHdl];
+ tab.element.append(dragHdl);
+
+ // highlight the tab when the tab document is brushed in any part of the UI
+ tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
+ titleEle.value = title;
+ titleEle.style.padding = degree ? 0 : 2;
+ titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
+ }, { fireImmediately: true });
+
+ // clean up the tab when it is closed
+ tab.closeElement.off('click') //unbind the current click handler
+ .click(function () {
+ Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+ Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
+ SelectionManager.DeselectAll();
+ tab.contentItem.remove();
+ });
+ }
+ }
+ /**
+ * Adds a document to the presentation view
+ **/
+ @undoBatch
+ @action
+ public static PinDoc(doc: Doc, unpin = false) {
+ if (unpin) TabDocView.UnpinDoc(doc);
+ else {
+ //add this new doc to props.Document
+ const curPres = CurrentUserUtils.ActivePresentation;
+ if (curPres) {
+ const pinDoc = Doc.MakeAlias(doc);
+ pinDoc.presentationTargetDoc = doc;
+ pinDoc.presZoomButton = true;
+ pinDoc.context = curPres;
+ Doc.AddDocToList(curPres, "data", pinDoc);
+ if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
+ if (!DocumentManager.Instance.getDocumentView(curPres)) {
+ CollectionDockingView.AddSplit(curPres, "right");
+ }
+ DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null));
+ }
+ }
+ }
+ /**
+ * Adds a document to the presentation view
+ **/
+ @undoBatch
+ @action
+ public static UnpinDoc(doc: Doc) {
+ //add this new doc to props.Document
+ const curPres = CurrentUserUtils.ActivePresentation;
+ if (curPres) {
+ const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc));
+ ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]);
+ }
+ }
+
+ componentDidMount() {
+ const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white");
+ const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document);
+ const updateTabColor = () => this.tab?.titleElement[0] && (this.tab.titleElement[0].style.backgroundColor = selected() ? color() : "");
+ const observer = new _global.ResizeObserver(action((entries: any) => {
+ for (const entry of entries) {
+ this._panelWidth = entry.contentRect.width;
+ this._panelHeight = entry.contentRect.height;
+ }
+ updateTabColor();
+ }));
+ observer.observe(this.props.glContainer._element[0]);
+ this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
+ this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged();
+ this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: color() }), updateTabColor, { fireImmediately: true });
+ }
+
+ componentWillUnmount() {
+ this._tabReaction?.();
+ this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
+ }
+
+ @action.bound
+ private onActiveContentItemChanged() {
+ if (this.props.glContainer.tab && this._isActive !== this.props.glContainer.tab.isActive) {
+ this._isActive = this.props.glContainer.tab.isActive;
+ this._isActive && setTimeout(() => this.view && SelectionManager.SelectDoc(this.view, false), 0);
+ (CollectionDockingView.Instance as any)._goldenLayout?.isInitialised && CollectionDockingView.Instance.stateChanged();
+ !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
+ }
+ }
+
+ get layoutDoc() { return this._document && Doc.Layout(this._document); }
+ 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;
+
+ contentScaling = () => {
+ const nativeH = this.nativeHeight();
+ const nativeW = this.nativeWidth();
+ let scaling = 1;
+ if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
+ scaling = 1;
+ } else if (NumCast(this.layoutDoc?._nativeWidth) && ((this.layoutDoc?._fitWidth) ||
+ this._panelHeight / NumCast(this.layoutDoc?._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc?._nativeWidth))) {
+ scaling = this._panelWidth / NumCast(this.layoutDoc?._nativeWidth);
+ } else if (nativeW && nativeH) {
+ const wscale = this.panelWidth() / nativeW;
+ scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+ }
+ return scaling;
+ }
+
+ ScreenToLocalTransform = () => {
+ if (this._mainCont?.children) {
+ const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0]?.firstChild as HTMLElement);
+ const scale = Utils.GetScreenTransform(this._mainCont).scale;
+ return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
+ }
+ 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; }
+
+ // adds a tab to the layout based on the locaiton parameter which can be:
+ // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab,
+ // add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
+ // replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
+ // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
+ // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
+ // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
+ addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
+ SelectionManager.DeselectAll();
+ if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+ const locationFields = location.split(":");
+ const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+ switch (locationFields[0]) {
+ case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
+ case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+ case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
+ case "inPlace":
+ case "add":
+ default: return CollectionDockingView.AddSplit(doc, locationParams, this.stack);
+ }
+ }
+
+ @computed get renderContentBounds() {
+ const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+ const xbounds = bounds[2] - bounds[0];
+ const ybounds = bounds[3] - bounds[1];
+ const dim = Math.max(xbounds, ybounds);
+ return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+ }
+ @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
+ @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
+ @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
+ miniDown = (e: React.PointerEvent) => {
+ this._document && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+ this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
+ this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
+ return false;
+ }), emptyFunction, emptyFunction);
+ }
+ getCurrentFrame = (): number => {
+ const presTargetDoc = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null);
+ return Cast(presTargetDoc._currentFrame, "number", null);
+ }
+
+ renderMiniMap() {
+ return <div className="miniMap" style={{
+ width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
+ StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!, 0))),
+ }}>
+ <CollectionFreeFormView
+ Document={this._document!}
+ LibraryPath={emptyPath}
+ CollectionView={undefined}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ChildLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid havin to set stuff like this.
+ noOverlay={true} // don't render overlay Docs since they won't scale
+ active={returnTrue}
+ select={emptyFunction}
+ dropAction={undefined}
+ isSelected={returnFalse}
+ dontRegisterView={true}
+ annotationsKey={""}
+ fieldKey={Doc.LayoutFieldKey(this._document!)}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ ContentScaling={returnOne}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={TabDocView.PinDoc}
+ docFilters={CollectionDockingView.Instance.docFilters}
+ searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+ fitToBox={true}
+ />
+ <div className="miniOverlay" onPointerDown={this.miniDown} >
+ <div className="miniThumb" style={{
+ width: `${this.miniWidth}% `,
+ height: `${this.miniHeight}% `,
+ left: `${this.miniLeft}% `,
+ top: `${this.miniTop}% `,
+ }}
+ />
+ </div>
+ </div>;
+ }
+ focusFunc = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => void) => afterFocus?.();
+ setView = action((view: DocumentView) => this._view = view);
+ @computed get docView() {
+ TraceMobx();
+ return !this._document || this._document._viewType === CollectionViewType.Docking ? (null) :
+ <><DocumentView key={this._document[Id]}
+ LibraryPath={emptyPath}
+ Document={this._document}
+ getView={this.setView}
+ DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ NativeHeight={this.nativeHeight}
+ NativeWidth={this.nativeWidth}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={this.focusFunc}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={TabDocView.PinDoc}
+ docFilters={CollectionDockingView.Instance.docFilters}
+ searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+ </>;
+ }
+
+ render() {
+ return (<div className="collectionDockingView-content" ref={ref => {
+ if (this._mainCont = ref) {
+ (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
+ this.tab && DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.init(this.tab, this._document)));
+ }
+ }}
+ style={{
+ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
+ height: this.layoutDoc?._fitWidth ? undefined : "100%",
+ width: this.widthpercent
+ }}>
+ {this.docView}
+ </div >);
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b00074cc6..bc2cb2d20 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -10,6 +10,7 @@ import { Id, ToString } from "../../../../fields/FieldSymbols";
import { ObjectField } from "../../../../fields/ObjectField";
import { RefField } from "../../../../fields/RefField";
import { listSpec } from "../../../../fields/Schema";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
export interface ViewDefBounds {
type: string;
@@ -359,7 +360,7 @@ export function computeTimelineLayout(
groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
}
- const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+ const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 46e30f616..306dfe797 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,4 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye } 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, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
@@ -15,7 +12,7 @@ import { ScriptField } 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, returnFalse, returnOne, returnZero, Utils, setupMoveUpEvents } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
@@ -24,10 +21,12 @@ import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager, dropActionType } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
import { InteractionUtils } from "../../../util/InteractionUtils";
+import { LinkManager } from "../../../util/LinkManager";
+import { SearchUtil } from "../../../util/SearchUtil";
import { SelectionManager } from "../../../util/SelectionManager";
import { SnappingManager } from "../../../util/SnappingManager";
import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { undoBatch } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { Timeline } from "../../animationtimeline/Timeline";
import { ContextMenu } from "../../ContextMenu";
@@ -37,29 +36,25 @@ import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
import { DocumentViewProps } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
+import { PresBox } from "../../nodes/PresBox";
import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
import { CollectionViewType } from "../CollectionView";
import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
-import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { PresBox } from "../../nodes/PresBox";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { LinkManager } from "../../../util/LinkManager";
-
-library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
- currentTimecode: "number",
+ _currentTimecode: "number",
displayTimecode: "number",
- currentFrame: "number",
+ _currentFrame: "number",
arrangeInit: ScriptField,
- useClusters: "boolean",
+ _useClusters: "boolean",
fitToBox: "boolean",
_xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
_yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
@@ -178,8 +173,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
newBox.y = y;
}
}
- if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
- CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame);
+ if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document._currentFrame, true);
}
}
return retVal;
@@ -189,7 +184,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
- public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+ public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document._currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
public getActiveDocuments = () => {
return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -212,9 +207,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
- if (this.Document.currentFrame !== undefined) {
+ if (this.Document._currentFrame !== undefined) {
const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
- CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.opacity);
+ CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.scroll, vals.opacity);
} else {
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
@@ -222,7 +217,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const nd = [NumCast(layoutDoc._nativeWidth), NumCast(layoutDoc._nativeHeight)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
- d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+ d._isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
@@ -261,7 +256,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- // if (this.props.Document.isBackground) return false;
+ // if (this.props.Document._isBackground) return false;
const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
if (this.isAnnotationOverlay !== true && de.complete.linkDragData) {
return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
@@ -306,8 +301,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
@action
- updateClusters(useClusters: boolean) {
- this.props.Document.useClusters = useClusters;
+ updateClusters(_useClusters: boolean) {
+ this.props.Document._useClusters = _useClusters;
this._clusterSets.length = 0;
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@@ -315,7 +310,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
updateClusterDocs(docs: Doc[]) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document.useClusters) {
+ if (this.props.Document._useClusters) {
const docFirst = docs[0];
docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
const preferredInd = NumCast(docFirst.cluster);
@@ -325,7 +320,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
docFirst.cluster = i;
}
})));
- if (docFirst.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
docFirst.cluster = preferredInd;
}
this._clusterSets.map((set, i) => {
@@ -338,7 +333,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
doc.cluster = this._clusterSets.length;
this._clusterSets.push([doc]);
});
- } else {
+ } else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= NumCast(docFirst.cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
}
@@ -350,7 +345,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
updateCluster(doc: Doc) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document.useClusters) {
+ if (this.props.Document._useClusters) {
this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
const preferredInd = NumCast(doc.cluster);
doc.cluster = -1;
@@ -359,7 +354,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
doc.cluster = i;
}
}));
- if (doc.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
doc.cluster = preferredInd;
}
this._clusterSets.map((set, i) => {
@@ -370,7 +365,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (doc.cluster === -1) {
doc.cluster = this._clusterSets.length;
this._clusterSets.push([doc]);
- } else {
+ } else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
this._clusterSets[doc.cluster].push(doc);
}
@@ -380,7 +375,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getClusterColor = (doc: Doc) => {
let clusterColor = this.props.backgroundColor?.(doc, this.props.renderDepth + 1);
const cluster = NumCast(doc.cluster);
- if (this.Document.useClusters) {
+ if (this.Document._useClusters) {
if (this._clusterSets.length <= cluster) {
setTimeout(() => this.updateCluster(doc), 0);
} else {
@@ -389,8 +384,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
clusterColor = colors[cluster % colors.length];
const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
// override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
- set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
- set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
+ set && set.filter(s => !s._isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
+ set && set.filter(s => s._isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
}
}
return clusterColor;
@@ -402,7 +397,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
return;
}
- this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
+ 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) {
@@ -591,7 +586,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onClick = (e: React.MouseEvent) => {
if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
if (Date.now() - this._lastTap < 300) {
- runInAction(() => DocumentLinksButton.StartLink = undefined);
+ runInAction(() => {
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ });
const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
this.scaleAtPt(docpt, 1);
e.stopPropagation();
@@ -605,7 +603,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
// bcz: theres should be a better way of doing these than referencing these static instances directly
MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
- // PDFMenu.Instance.fadeOut(true); (commented out for mobile)
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
@@ -641,7 +638,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- // panning a workspace
if (!e.cancelBubble) {
const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt = myTouches[0];
@@ -843,14 +839,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
bringToFront = action((doc: Doc, sendToBack?: boolean) => {
- if (sendToBack || doc.isBackground) {
+ if (sendToBack || doc._isBackground) {
doc.zIndex = 0;
} else if (doc.isInkMask) {
doc.zIndex = 5000;
} else {
const docs = this.childLayoutPairs.map(pair => pair.layout);
docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1;
+ let zlast = docs.length ? Math.max(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;
@@ -908,10 +904,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
- // if (!willZoom && DocumentView._focusHack.length) {
- // Doc.BrushDoc(this.props.Document);
- // !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
- // } else {
if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
// glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
if (!doc.z) this.setPan(newPanX, newPanY, doc.presTransition || doc.presTransition === 0 ? `transform ${doc.presTransition}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
@@ -920,7 +912,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.focus(this.props.Document);
willZoom && this.setScaleToZoom(layoutdoc, scale);
Doc.linkFollowHighlight(doc);
- //}
afterFocus && setTimeout(() => {
if (afterFocus?.()) {
@@ -939,10 +930,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
- @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+ @computed get backgroundActive() { return this.layoutDoc._isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
- backgroundHalo = () => BoolCast(this.Document.useClusters);
+ backgroundHalo = () => BoolCast(this.Document._useClusters);
parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
@@ -975,6 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.Document,
docFilters: this.docFilters,
+ searchFilterDocs: this.searchFilterDocs,
focus: this.focusDocument,
backgroundColor: this.getClusterColor,
backgroundHalo: this.backgroundHalo,
@@ -1008,8 +1000,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
});
getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
const layoutDoc = Doc.Layout(params.pair.layout);
- const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
- CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
+ const { x, y, opacity } = this.Document._currentFrame === undefined ? params.pair.layout :
+ CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame || 0);
const { z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
@@ -1045,7 +1037,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
} else if (viewDef.type === "div") {
return [x, y].some(val => val === undefined) ? undefined :
{
- ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
+ ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z + viewDef.payload} onClick={e => this.onViewDefDivClick(e, viewDef)}
style={{ width, height, backgroundColor: color, transform }} />,
bounds: viewDef
};
@@ -1140,7 +1132,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
}
- this.Document.useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
+ this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
}
@@ -1175,7 +1167,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
(e as any).handlePan = true;
- if (this._marqueeRef?.current) {
+ if (!this.props.Document._noAutoscroll && this._marqueeRef?.current) {
const dragX = e.detail.clientX;
const dragY = e.detail.clientY;
const bounds = this._marqueeRef.current?.getBoundingClientRect();
@@ -1252,15 +1244,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
!Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null;
- !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }) : null;
+ !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null;
!viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems = options && "subitems" in options ? options.subitems : [];
!this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
- optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
+ optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: "eye" });
this.props.ContainingCollectionView &&
- optionItems.push({ description: "Undo Collection", event: this.promoteCollection, icon: "table" });
+ optionItems.push({ description: "Move Items Out of Collection", event: this.promoteCollection, icon: "table" });
optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
if (!Doc.UserDoc().noviceMode) {
@@ -1315,7 +1307,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
@action
- setupDragLines = () => {
+ setupDragLines = (snapToDraggedDoc: boolean = false) => {
const activeDocs = this.getActiveDocuments();
if (activeDocs.length > 50) {
DragManager.SetSnapLines([], []);
@@ -1331,13 +1323,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
};
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
+ 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 => {
+ snappableDocs.filter(doc => snapToDraggedDoc || !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);
@@ -1349,7 +1341,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
onPointerOver = (e: React.PointerEvent) => {
if (SnappingManager.GetIsDragging()) {
- this.setupDragLines();
+ this.setupDragLines(e.ctrlKey || e.shiftKey);
}
e.stopPropagation();
}
@@ -1406,12 +1398,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
centeringShiftY={this.centeringShiftY}
presPaths={BoolCast(this.Document.presPathView)}
progressivize={BoolCast(this.Document.editProgressivize)}
- zoomProgressivize={BoolCast(this.Document.editZoomProgressivize)}
+ presPinView={BoolCast(this.Document.presPinView)}
transition={Cast(this.layoutDoc._viewTransition, "string", null)}
viewDefDivClick={this.props.viewDefDivClick}
zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
- </CollectionFreeFormViewPannableContents></div>
+ </CollectionFreeFormViewPannableContents>
+ </div>
{this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
@@ -1424,7 +1417,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 && SnappingManager.GetIsDragging(); }
+ @computed get backgroundEvents() { return this.layoutDoc._isBackground && SnappingManager.GetIsDragging(); }
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1492,7 +1485,7 @@ interface CollectionFreeFormViewPannableContentsProps {
transition?: string;
presPaths?: boolean;
progressivize?: boolean;
- zoomProgressivize?: boolean;
+ presPinView?: boolean;
}
@observer
@@ -1559,48 +1552,27 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
doc.style.top = toNumber(top, e.movementY) + 'px';
doc.style.left = toNumber(left, e.movementX) + 'px';
}
- this.updateAll(height, width, top, left);
+ // this.updateAll(height, width, top, left);
return false;
}
return true;
}
- @action
- updateAll = (width: number, height: number, top: number, left: number) => {
- const activeItem = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
- this.updateList(targetDoc, activeItem["viewfinder-width-indexed"], width);
- this.updateList(targetDoc, activeItem["viewfinder-height-indexed"], height);
- this.updateList(targetDoc, activeItem["viewfinder-top-indexed"], top);
- this.updateList(targetDoc, activeItem["viewfinder-left-indexed"], left);
- }
-
- @action
- updateList = (doc: Doc, list: any, val: number) => {
- const x: List<number> = list;
- if (x && x.length >= NumCast(doc.currentFrame) + 1) {
- x[NumCast(doc.currentFrame)] = val;
- list = x;
- } else if (doc && x) {
- x.length = NumCast(doc.currentFrame) + 1;
- x[NumCast(doc.currentFrame)] = val;
- list = x;
- }
- }
-
// scale: NumCast(targetDoc._viewScale),
@computed get zoomProgressivizeContainer() {
- const activeItem = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
- if (activeItem && activeItem.zoomProgressivize) {
- const vfLeft: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-left-indexed"]);
- const vfWidth: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-width-indexed"]);
- const vfTop: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-top-indexed"]);
- const vfHeight: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-height-indexed"]);
+ const activeItem = PresBox.Instance.activeItem;
+ // const targetDoc = PresBox.Instance.targetDoc;
+ if (activeItem && activeItem.presPinView && activeItem.id) {
+ const vfLeft: number = NumCast(activeItem.presPinViewX);
+ const vfTop: number = NumCast(activeItem.presPinViewY);
+ const vfWidth: number = 100;
+ const vfHeight: number = 100;
+ console.log(vfTop + " | " + vfLeft);
+ console.log(this.props.presPinView);
return (
<>
- {!activeItem.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}>
- <div className='resizers'>
+ {!this.props.presPinView ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}>
+ <div className='resizers' key={'resizer' + activeItem.id}>
<div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerDown}></div>
<div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerDown}></div>
<div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerDown}></div>
@@ -1613,7 +1585,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
}
@computed get zoomProgressivize() {
- return PresBox.Instance && this.props.zoomProgressivize ? this.zoomProgressivizeContainer : (null);
+ return PresBox.Instance && PresBox.Instance.activeItem && PresBox.Instance.activeItem.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : (null);
}
@computed get progressivize() {
@@ -1622,30 +1594,30 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
@computed get presPaths() {
const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden");
- return !(PresBox.Instance) ? (null) : (<>
- {!this.props.presPaths ? (null) : <><div>{PresBox.Instance.order}</div>
- <svg className={presPaths}>
- <defs>
- <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
- <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" />
- </marker>
- <marker id="square" markerWidth="3" markerHeight="3" overflow="visible"
- refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
- <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" />
- </marker>
- <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4"
- orient="auto" overflow="visible">
- <rect x="1" y="1" width="5" height="5" fill="#69a6db" />
- </marker>
-
- <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7"
- orient="auto" overflow="visible">
- <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" />
- </marker>
- </defs>;
- {PresBox.Instance.paths}
- </svg></>}
- </>);
+ return !PresBox.Instance || !this.props.presPaths ? (null) : <>
+ <div key="presorder">{PresBox.Instance.order}</div>
+ <svg key="svg" className={presPaths}>
+ <defs>
+ <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" />
+ </marker>
+ <marker id="square" markerWidth="3" markerHeight="3" overflow="visible"
+ refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
+ <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" />
+ </marker>
+ <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4"
+ orient="auto" overflow="visible">
+ <rect x="1" y="1" width="5" height="5" fill="#69a6db" />
+ </marker>
+
+ <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7"
+ orient="auto" overflow="visible">
+ <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" />
+ </marker>
+ </defs>
+ {PresBox.Instance.paths}
+ </svg>
+ </>;
}
render() {
@@ -1659,7 +1631,8 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
return <div className={freeformclass}
style={{
transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`,
- transition: this.props.transition
+ transition: this.props.transition,
+ //willChange: "transform"
}}>
{this.props.children()}
{this.presPaths}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
deleted file mode 100644
index d49ab27fb..000000000
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-.antimodeMenu-button {
- width: 200px;
- position: relative;
- text-align: left;
-
- .color-previewI {
- width: 100%;
- height: 40%;
- }
-
- .color-previewII {
- width: 100%;
- height: 100%;
- }
-}
-
-.antimenu-Buttonup {
- position: absolute;
- width: 20;
- height: 10;
- right: 0;
- padding: 0;
-}
-
-.formatShapePane-inputBtn {
- width: inherit;
- position: absolute;
-}
-
-.btn-group-palette {
- .sketch-picker {
- background: #323232;
- width: 160px !important;
- height: 80% !important;
-
- .flexbox-fit {
- background: #323232;
- }
- }
-}
-
-.btn-group {
- display: grid;
- grid-template-columns: auto auto auto auto;
- /* Make the buttons appear below each other */
-}
-
-.btn-group-palette {
- display: block;
- /* Make the buttons appear below each other */
-}
-
-.btn-draw {
- display: inline;
- /* Make the buttons appear below each other */
-}
-
-.btn2-group {
- display: block;
- background: #323232;
- grid-template-columns: auto;
-
- /* Make the buttons appear below each other */
- .antimodeMenu-button {
- background: #323232;
- display: block;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
deleted file mode 100644
index 1ffa2fbed..000000000
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ /dev/null
@@ -1,558 +0,0 @@
-import React = require("react");
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field, Opt } from "../../../../fields/Doc";
-import { Document } from "../../../../fields/documentSchemas";
-import { InkField } from "../../../../fields/InkField";
-import { BoolCast, Cast, NumCast } from "../../../../fields/Types";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { SelectionManager } from "../../../util/SelectionManager";
-import AntimodeMenu from "../../AntimodeMenu";
-import "./FormatShapePane.scss";
-import { undoBatch } from "../../../util/UndoManager";
-import { ColorState, SketchPicker } from 'react-color';
-
-@observer
-export default class FormatShapePane extends AntimodeMenu {
- static Instance: FormatShapePane;
-
- private _lastFill = "#D0021B";
- private _lastLine = "#D0021B";
- private _lastDash = "2";
- private _mode = ["fill-drip", "ruler-combined"];
-
- @observable private _subOpen = [false, false];
- @observable private _currMode = "fill-drip";
- @observable _lock = false;
- @observable private _fillBtn = false;
- @observable private _lineBtn = false;
- @observable _controlBtn = false;
- @observable private _controlPoints: { X: number, Y: number }[] = [];
- @observable _currPoint = -1;
-
- getField(key: string) {
- return this.selectedInk?.reduce((p, i) =>
- (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
- }
-
- @computed get selectedInk() {
- const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
- return inks.length ? inks : undefined;
- }
- @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
- @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
- @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
- @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
- @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
- @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
- @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
- @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
- @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
- @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
- @computed get shapeHgt() { return this.getField("_height"); }
- @computed get shapeWid() { return this.getField("_width"); }
- @computed get shapeXps() { return this.getField("x"); }
- @computed get shapeYps() { return this.getField("y"); }
- @computed get shapeRot() { return this.getField("rotation"); }
- set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
- set solidFil(value) { this.unFilled = !value; }
- set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
- set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
- set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
- set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
- set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
- set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
- set dashdStk(value) {
- value && (this._lastDash = value) && (this.unStrokd = false);
- this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
- }
- set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
- set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
- set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
- set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
- set shapeWid(value) {
- this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
- const oldWidth = NumCast(i.rootDoc._width);
- i.rootDoc._width = Number(value);
- this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
- });
- }
- set shapeHgt(value) {
- this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
- const oldHeight = NumCast(i.rootDoc._height);
- i.rootDoc._height = Number(value);
- this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
- });
- }
-
- constructor(props: Readonly<{}>) {
- super(props);
- FormatShapePane.Instance = this;
- this._canFade = false;
- this.Pinned = BoolCast(Doc.UserDoc()["menuFormatShape-pinned"]);
- }
-
- @action
- closePane = () => {
- this.fadeOut(false);
- this.Pinned = false;
- }
-
- @action
- upDownButtons = (dirs: string, field: string) => {
- switch (field) {
- case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
- // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break;
- case "Xps": this.selectedInk?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break;
- case "Yps": this.selectedInk?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break;
- case "stk": this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
- case "wid": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
- //redraw points
- const oldWidth = NumCast(i.rootDoc._width);
- const oldHeight = NumCast(i.rootDoc._height);
- const oldX = NumCast(i.rootDoc.x);
- const oldY = NumCast(i.rootDoc.y);
- i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
- this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
- const doc = Document(i.rootDoc);
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
- console.log(doc.x, doc.y, doc._height, doc._width);
- const ink = Cast(doc.data, InkField)?.inkData;
- console.log(ink);
- if (ink) {
- const newPoints: { X: number, Y: number }[] = [];
- ink.forEach(i => {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
- const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
- newPoints.push({ X: newX, Y: newY });
- });
- doc.data = new InkField(newPoints);
- }
- }
- });
- break;
- case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
- const oldWidth = NumCast(i.rootDoc._width);
- const oldHeight = NumCast(i.rootDoc._height);
- const oldX = NumCast(i.rootDoc.x);
- const oldY = NumCast(i.rootDoc.y); i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
- this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
- const doc = Document(i.rootDoc);
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
- console.log(doc.x, doc.y, doc._height, doc._width);
- const ink = Cast(doc.data, InkField)?.inkData;
- console.log(ink);
- if (ink) {
- const newPoints: { X: number, Y: number }[] = [];
- ink.forEach(i => {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
- const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
- newPoints.push({ X: newX, Y: newY });
- });
- doc.data = new InkField(newPoints);
- }
- }
- });
- break;
- }
- }
-
- @undoBatch
- @action
- addPoints = (x: number, y: number, pts: { X: number, Y: number }[], index: number, control: { X: number, Y: number }[]) => {
- this.selectedInk?.forEach(action(inkView => {
- if (this.selectedInk?.length === 1) {
- const doc = Document(inkView.rootDoc);
- if (doc.type === DocumentType.INK) {
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
- const newPoints: { X: number, Y: number }[] = [];
- var counter = 0;
- for (var k = 0; k < index; k++) {
- control.forEach(pt => (pts[k].X === pt.X && pts[k].Y === pt.Y) && counter++);
- }
- //decide where to put the new coordinate
- const spNum = Math.floor(counter / 2) * 4 + 2;
-
- for (var i = 0; i < spNum; i++) {
- newPoints.push({ X: ink[i].X, Y: ink[i].Y });
- }
- for (var j = 0; j < 4; j++) {
- newPoints.push({ X: x, Y: y });
-
- }
- for (var i = spNum; i < ink.length; i++) {
- newPoints.push({ X: ink[i].X, Y: ink[i].Y });
- }
- this._currPoint = -1;
- doc.data = new InkField(newPoints);
- }
- }
- }
- }));
- }
-
- @undoBatch
- @action
- deletePoints = () => {
- this.selectedInk?.forEach(action(inkView => {
- if (this.selectedInk?.length === 1 && this._currPoint !== -1) {
- const doc = Document(inkView.rootDoc);
- if (doc.type === DocumentType.INK) {
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink && ink.length > 4) {
- const newPoints: { X: number, Y: number }[] = [];
-
- console.log(ink.length, this._currPoint, Math.floor((this._currPoint + 2) / 4));
-
- const toRemove = Math.floor(((this._currPoint + 2) / 4));
- for (var i = 0; i < ink.length; i++) {
- if (Math.floor((i + 2) / 4) !== toRemove) {
- console.log(i, toRemove);
- newPoints.push({ X: ink[i].X, Y: ink[i].Y });
- }
- }
- this._currPoint = -1;
- doc.data = new InkField(newPoints);
- if (newPoints.length === 4) {
- const newerPoints: { X: number, Y: number }[] = [];
- newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
- newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
- newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
- newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
- doc.data = new InkField(newerPoints);
-
- }
- }
- }
- }
- }));
- }
-
- @undoBatch
- @action
- rotate = (angle: number) => {
- const _centerPoints: { X: number, Y: number }[] = [];
- SelectionManager.SelectedDocuments().forEach(action(inkView => {
- const doc = Document(inkView.rootDoc);
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
- const xs = ink.map(p => p.X);
- const ys = ink.map(p => p.Y);
- const left = Math.min(...xs);
- const top = Math.min(...ys);
- const right = Math.max(...xs);
- const bottom = Math.max(...ys);
- _centerPoints.push({ X: left, Y: top });
- }
- }
- }));
-
- var index = 0;
- SelectionManager.SelectedDocuments().forEach(action(inkView => {
- const doc = Document(inkView.rootDoc);
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
- doc.rotation = Number(doc.rotation) + Number(angle);
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
-
- const newPoints: { X: number, Y: number }[] = [];
- ink.forEach(i => {
- const newX = Math.cos(angle) * (i.X - _centerPoints[index].X) - Math.sin(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].X;
- const newY = Math.sin(angle) * (i.X - _centerPoints[index].X) + Math.cos(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
- newPoints.push({ X: newX, Y: newY });
- });
- doc.data = new InkField(newPoints);
- const xs = newPoints.map(p => p.X);
- const ys = newPoints.map(p => p.Y);
- const left = Math.min(...xs);
- const top = Math.min(...ys);
- const right = Math.max(...xs);
- const bottom = Math.max(...ys);
-
- doc._height = (bottom - top);
- doc._width = (right - left);
- }
- index++;
- }
- }));
- }
-
- @undoBatch
- @action
- control = (xDiff: number, yDiff: number, controlNum: number) => {
- this.selectedInk?.forEach(action(inkView => {
- if (this.selectedInk?.length === 1) {
- const doc = Document(inkView.rootDoc);
- if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
-
- const newPoints: { X: number, Y: number }[] = [];
- const order = controlNum % 4;
- for (var i = 0; i < ink.length; i++) {
- if (controlNum === i ||
- (order === 0 && i === controlNum + 1) ||
- (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
- (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
- (order === 3 && i === controlNum - 1) ||
- (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
- (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)
- || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
- ) {
- newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) });
- }
- else {
- newPoints.push({ X: ink[i].X, Y: ink[i].Y });
- }
- }
- const oldx = doc.x;
- const oldy = doc.y;
- const xs = ink.map(p => p.X);
- const ys = ink.map(p => p.Y);
- const left = Math.min(...xs);
- const top = Math.min(...ys);
- doc.data = new InkField(newPoints);
- const xs2 = newPoints.map(p => p.X);
- const ys2 = newPoints.map(p => p.Y);
- const left2 = Math.min(...xs2);
- const top2 = Math.min(...ys2);
- const right2 = Math.max(...xs2);
- const bottom2 = Math.max(...ys2);
- doc._height = (bottom2 - top2);
- doc._width = (right2 - left2);
- //if points move out of bounds
-
- doc.x = oldx - (left - left2);
- doc.y = oldy - (top - top2);
-
- }
- }
- }
- }));
- }
-
- @undoBatch
- @action
- switchStk = (color: ColorState) => {
- const val = String(color.hex);
- this.colorStk = val;
- return true;
- }
-
- @undoBatch
- @action
- switchFil = (color: ColorState) => {
- const val = String(color.hex);
- this.colorFil = val;
- return true;
- }
-
-
- colorPicker(setter: (color: string) => {}, type: string) {
- return <div className="btn-group-palette" key="colorpicker" style={{ width: 160, margin: 10 }}>
- <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- color={type === "stk" ? this.colorStk : this.colorFil} />
- </div>;
- }
- inputBox = (key: string, value: any, setter: (val: string) => {}) => {
- return <>
- <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
- type="text" value={value}
- onChange={undoBatch(action((e) => setter(e.target.value)))}
- autoFocus />
- <button className="antiMenu-Buttonup" key="up1" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
- Ë„
- </button>
- <br />
- <button className="antiMenu-Buttonup" key="down1" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
- Ë…
- </button>
- </>;
- }
-
- inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
- return <>
- {title1}
- <p style={{ marginTop: -20, right: 70, position: "absolute" }}>{title2}</p>
-
- <input style={{ color: "black", width: 40, position: "absolute", right: 130 }}
- type="text" value={value}
- onChange={e => setter(e.target.value)}
- autoFocus />
- <button className="antiMenu-Buttonup" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} style={{ right: 110 }}>
- Ë„
- </button>
- <button className="antiMenu-Buttonup" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: 12, right: 110 }}>
- Ë…
- </button>
- {title2 === "" ? "" : <>
- <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
- type="text" value={value2}
- onChange={e => setter2(e.target.value)}
- autoFocus />
- <button className="antiMenu-Buttonup" key="up3" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key2)))}>
- Ë„
- </button>
- <br />
- <button className="antiMenu-Buttonup" key="down3" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key2)))} style={{ marginTop: -8 }}>
- Ë…
- </button></>}
- </>;
- }
-
-
- colorButton(value: string, setter: () => {}) {
- return <>
- <button className="antimodeMenu-button" key="color" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "relative", marginTop: -5 }}>
- <div className="color-previewII" style={{ backgroundColor: value ?? "121212" }} />
- {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -23, position: "fixed" }}>☒</p> : ""}
- </button>
- </>;
- }
-
- controlPointsButton() {
- return <>
- <button className="antimodeMenu-button" title="Edit points" key="bezier" onPointerDown={action(() => this._controlBtn = this._controlBtn ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._controlBtn ? "black" : "" }}>
- <FontAwesomeIcon icon="bezier-curve" size="lg" />
- </button>
- <button className="antimodeMenu-button" title="Lock ratio" key="ratio" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._lock ? "black" : "" }}>
- <FontAwesomeIcon icon="lock" size="lg" />
-
- </button>
- <button className="antimodeMenu-button" key="rotate" title="Rotate 90Ëš" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "relative", marginTop: 10, fontSize: 15 }}>
- ⟲
- </button>
- <br /> <br />
- </>;
- }
-
- lockRatioButton() {
- return <>
- <button className="antimodeMenu-button" key="lock" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "absolute", right: 80, backgroundColor: this._lock ? "black" : "" }}>
- {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
- <FontAwesomeIcon icon="lock" size="lg" />
-
- </button>
- <br /> <br />
- </>;
- }
-
- rotate90Button() {
- return <>
- <button className="antimodeMenu-button" key="rot" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "absolute", right: 80, }}>
- {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
- ⟲
-
- </button>
- <br /> <br />
- </>;
- }
- @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
- @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
-
- @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
- @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
-
- @computed get stkInput() { return this.inputBox("stk", this.widthStk, (val: string) => this.widthStk = val); }
- @computed get dashInput() { return this.inputBox("dsh", this.widthStk, (val: string) => this.widthStk = val); }
-
- @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val, "H:", "wid", this.shapeWid, (val: string) => this.shapeWid = val, "W:"); }
- @computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = val); }
- @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; return true; }, "∠:", "rot", this.shapeRot, (val: string) => this.shapeRot = val, ""); }
-
- @computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
-
- @computed get controlPoints() { return this.controlPointsButton(); }
- @computed get lockRatio() { return this.lockRatioButton(); }
- @computed get rotate90() { return this.rotate90Button(); }
- @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); }
-
-
- @computed get propertyGroupItems() {
- const fillCheck = <div key="fill" style={{ display: (this._subOpen[0] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- Fill:
- {this.fillButton}
- <div style={{ float: "left", width: 100 }} >
- Stroke:
- {this.lineButton}
- </div>
-
- {this._fillBtn ? this.fillPicker : ""}
- {this._lineBtn ? this.linePicker : ""}
- {this._fillBtn || this._lineBtn ? "" : <br />}
- {(this.solidStk || this.dashdStk) ? "Width" : ""}
- {(this.solidStk || this.dashdStk) ? this.stkInput : ""}
-
-
- {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={undoBatch(action((e) => this.widthStk = e.target.value))} /> : (null)}
- <br />
- {(this.solidStk || this.dashdStk) ? <>
- <p style={{ position: "absolute", fontSize: 12 }}>Arrow Head</p>
- <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} style={{ position: "absolute", right: 110, width: 20 }} />
- <p style={{ position: "absolute", fontSize: 12, right: 30 }}>Arrow End</p>
- <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} style={{ position: "absolute", right: 0, width: 20 }} />
- <br />
- </> : ""}
- Dash: <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.dashdStk === "2"} onChange={undoBatch(action(() => this.dashdStk = this.dashdStk === "2" ? "0" : "2"))} style={{ position: "absolute", right: 110, width: 20 }} />
-
-
-
- </div>;
-
-
-
- const sizeCheck =
-
- <div key="sizeCheck" style={{ display: (this._subOpen[1] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- {this.controlPoints}
- {this.hgtInput}
- {this.XpsInput}
- {this.rotInput}
-
- </div>;
-
-
- const subMenus = this._currMode === "fill-drip" ? [`Appearance`, 'Transform'] : [];
- const menuItems = this._currMode === "fill-drip" ? [fillCheck, sizeCheck] : [];
- const indexOffset = 0;
-
- return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
- {subMenus.map((subMenu, i) =>
- <div key={subMenu} style={{ width: "inherit" }}>
- <button className="antimodeMenu-button" onPointerDown={action(() => this._subOpen[i + indexOffset] = !this._subOpen[i + indexOffset])}
- style={{ backgroundColor: "121212", position: "relative", width: "inherit" }}>
- {this._subOpen[i + indexOffset] ? "▼" : "▶︎"}
- {subMenu}
- </button>
- {menuItems[i]}
- </div>)}
- </div>;
- }
-
- @computed get closeBtn() {
- return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}>
- X
- </button>;
- }
-
- @computed get propertyGroupBtn() {
- return <div className="antimodeMenu-button-tab" key="modes">
- {this._mode.map(mode =>
- <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => this._currMode = mode)}
- style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
- <FontAwesomeIcon icon={mode as IconProp} size="lg" />
- </button>)}
- </div>;
- }
-
- render() {
- return this.getElementVert([this.closeBtn,
- this.propertyGroupItems]);
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index f1df7998b..46298ec6f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -1,13 +1,12 @@
import React = require("react");
-import AntimodeMenu from "../../AntimodeMenu";
-import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { unimplementedFunction } from "../../../../Utils";
-import { undoBatch } from "../../../util/UndoManager";
import { Tooltip } from "@material-ui/core";
+import { observer } from "mobx-react";
+import { unimplementedFunction } from "../../../../Utils";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
@observer
-export default class MarqueeOptionsMenu extends AntimodeMenu {
+export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: MarqueeOptionsMenu;
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
@@ -16,6 +15,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
+ public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
constructor(props: Readonly<{}>) {
super(props);
@@ -53,6 +53,14 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
<FontAwesomeIcon icon="font" size="lg" />
</button>
</Tooltip>,
+ <Tooltip key="pinWithView" title={<><div className="dash-tooltip">Pin to presentation with selected view</div></>} placement="bottom">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={this.pinWithView}>
+ <FontAwesomeIcon icon="map-pin" size="lg" />
+ <div style={{ position: 'relative', fontSize: 25, fontWeight: 700, transform: 'translate(-4px, -22px)', color: 'rgba(250,250,250,0.55)' }}>V</div>
+ </button>
+ </Tooltip>,
];
return this.getElement(buttons);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c0b19fcd2..d8e1bcc9c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,27 +1,28 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
-import { GetEffectiveAcl } from "../../../../fields/util";
+import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc";
import { InkData, InkField, InkTool } 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 { GetEffectiveAcl } from "../../../../fields/util";
import { Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
+import { DocumentManager } from "../../../util/DocumentManager";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { ContextMenu } from "../../ContextMenu";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { PreviewCursor } from "../../PreviewCursor";
+import { CollectionDockingView } from "../CollectionDockingView";
import { SubCollectionViewProps } from "../CollectionSubView";
-import { CollectionView } from "../CollectionView";
-import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import { CollectionView, CollectionViewType } from "../CollectionView";
+import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
import "./MarqueeView.scss";
import React = require("react");
-import { ContextMenuItem } from "../../ContextMenuItem";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -75,7 +76,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
if (e.key === "?") {
cm.setDefaultItem("?", (str: string) => this.props.addDocTab(
- Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "onRight"));
+ Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", useCors: true }), "add:right"));
cm.displayMenu(this._downX, this._downY);
e.stopPropagation();
@@ -130,6 +131,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const tbox = Docs.Create.TextDocument("", {
_width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
_fontFamily: StrCast(Doc.UserDoc().fontFamily),
+ _showTitle: Doc.UserDoc().showTitle ? "title" : undefined,
title: "-typed text-"
});
const template = FormattedTextBox.DefaultLayout;
@@ -138,6 +140,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
tbox.layoutKey = "layout_" + StrCast(template.title);
Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
}
+ FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch");
this.props.addLiveTextDocument(tbox);
e.stopPropagation();
}
@@ -241,6 +244,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
MarqueeOptionsMenu.Instance.fadeOut(true);
document.removeEventListener("pointerdown", hideMarquee);
+ document.removeEventListener("wheel", hideMarquee);
};
if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
MarqueeOptionsMenu.Instance.createCollection = this.collection;
@@ -250,7 +254,9 @@ 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);
+ MarqueeOptionsMenu.Instance.pinWithView = this.pinWithView;
document.addEventListener("pointerdown", hideMarquee);
+ document.addEventListener("wheel", hideMarquee);
} else {
this.hideMarquee();
}
@@ -349,7 +355,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => {
+ getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, _isBackground?: boolean) => {
const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => {
Doc.GetProto(doc).data = new List<Doc>(selected);
Doc.GetProto(doc).title = "nested freeform";
@@ -357,8 +363,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return doc;
})(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
newCollection.system = undefined;
- newCollection.isBackground = isBackground;
- newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined;
+ newCollection._isBackground = _isBackground;
+ newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : _isBackground ? "cyan" : undefined;
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
newCollection.x = this.Bounds.left;
@@ -381,6 +387,38 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@undoBatch @action
+ pinWithView = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const doc = this.props.Document;
+ const bounds = this.Bounds;
+ const selected = this.marqueeSelect(false);
+ 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 (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
+ if (!DocumentManager.Instance.getDocumentView(curPres)) {
+ CollectionDockingView.AddSplit(curPres, "right");
+ }
+ if (e instanceof KeyboardEvent ? e.key === "c" : true) {
+ const x = this.Bounds.left + this.Bounds.width / 2;
+ const y = this.Bounds.top + this.Bounds.height / 2;
+ const panelWidth: number = this.props.PanelWidth();
+ const panelHeight: number = this.props.PanelHeight();
+ const scale = Math.min(Number(panelWidth) / this.Bounds.width, Number(panelHeight) / this.Bounds.height);
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewX = x;
+ pinDoc.presPinViewY = y;
+ pinDoc.presPinViewScale = scale;
+ }
+ }
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ @undoBatch @action
collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
@@ -492,7 +530,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.page = -1;
return d;
});
- const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
+ const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
const portal = Doc.MakeAlias(summary);
Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
@@ -644,7 +682,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).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);
@@ -719,14 +757,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
</div>;
} else {
- //subtracted 250 for offset
var str: string = "";
for (var i = 0; i < this._pointsX.length; i++) {
- var x = 0;
- x = this._pointsX[i] - 250;
- str += x.toString();
+ const pt = this.props.getContainerTransform().transformPoint(this._pointsX[i], this._pointsY[i]);
+ str += pt[0].toString();
str += ",";
- str += this._pointsY[i].toString();
+ str += pt[1].toString();
str += (" ");
}
@@ -747,7 +783,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
render() {
return <div className="marqueeView"
- style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
+ style={{ overflow: !this.props.ContainingCollectionView && this.props.annotationsKey ? "visible" : StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
onDragOver={e => e.preventDefault()}
onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index 4d8473be9..60ec02f47 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -134,17 +134,6 @@
}
-// .documentDecorations-container .documentDecorations-resizer {
-// pointer-events: none;
-// }
-
-// #documentDecorations-bottomRightResizer,
-// #documentDecorations-bottomLeftResizer,
-// #documentDecorations-topRightResizer,
-// #documentDecorations-topLeftResizer {
-// visibility: collapse;
-// }
-
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index e6ac7021a..4e279c659 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -94,8 +94,8 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
*/
unflexedPosition(index: number): Omit<Layout, "i"> {
return {
- x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW,
- y: Math.floor(index / Math.floor(this.numCols / this.defaultH)) * this.defaultH,
+ x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW,
+ y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH,
w: this.defaultW,
h: this.defaultH,
static: true
@@ -304,7 +304,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
(e: PointerEvent, doubleTap?: boolean) => {
if (doubleTap) {
undoBatch(action(() => {
- const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
+ const text = Docs.Create.TextDocument("", { _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, _width: 150, _height: 50 });
FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 21d283547..0afcab5a3 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -234,6 +234,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
ScreenToLocalTransform={dxf}
focus={this.props.focus}
docFilters={this.docFilters}
+ searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index d02088a6c..53825eece 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -234,6 +234,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
ScreenToLocalTransform={dxf}
focus={this.props.focus}
docFilters={this.docFilters}
+ searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 7eb07ff55..d0c90edcb 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -36,6 +36,12 @@ $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should
$COLLECTION_BORDER_WIDTH: 1;
$MINIMIZED_ICON_SIZE:25;
$MAX_ROW_HEIGHT: 44px;
+$DFLT_IMAGE_NATIVE_DIM: 900px;
+$MENU_PANEL_WIDTH: 60px;
+:root {
+ --flyoutHandleWidth: 28px;
+ --menuPanelWidth: 60px;
+}
:export {
contextMenuZindex: $contextMenu-zindex;
@@ -45,4 +51,6 @@ $MAX_ROW_HEIGHT: 44px;
SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
ANTIMODEMENU_HEIGHT: $antimodemenu-height;
SEARCH_PANEL_HEIGHT: $searchpanel-height;
+ DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
+ MENU_PANEL_WIDTH: $MENU_PANEL_WIDTH;
} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index fd1f60f13..fb3a327d0 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -7,6 +7,8 @@ interface IGlobalScss {
SEARCH_THUMBNAIL_SIZE: string;
ANTIMODEMENU_HEIGHT: string;
SEARCH_PANEL_HEIGHT: string;
+ DFLT_IMAGE_NATIVE_DIM: string;
+ MENU_PANEL_WIDTH: string;
}
declare const globalCssVariables: IGlobalScss;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ed64bde32..d6c3702d1 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,5 +1,3 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, observable } from "mobx";
@@ -12,9 +10,6 @@ import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
import React = require("react");
-library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
-
-
interface GroupTypesDropdownProps {
groupType: string;
setGroupType: (group: string) => void;
@@ -369,7 +364,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-followingDropdown-dropdown">
<div className="linkEditor-followingDropdown-header"
onPointerDown={this.changeDropdown}>
- {StrCast(this.props.linkDoc.followLinkLocation, "Default")}
+ {StrCast(this.props.linkDoc.followLinkLocation, "default")}
<FontAwesomeIcon className="linkEditor-followingDropdown-icon"
icon={this.openDropdown ? "chevron-up" : "chevron-down"}
size={"lg"} />
@@ -377,17 +372,37 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-followingDropdown-optionsList"
style={{ display: this.openDropdown ? "" : "none" }}>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("Default")}>
+ onPointerDown={() => this.changeFollowBehavior("default")}>
Default
</div>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("onRight")}>
- Always open in a new pane
+ onPointerDown={() => this.changeFollowBehavior("add:left")}>
+ Always open in new left pane
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("add:right")}>
+ Always open in new right pane
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("replace:right")}>
+ Always replace right tab
</div>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("inTab")}>
+ onPointerDown={() => this.changeFollowBehavior("replace:left")}>
+ Always replace left tab
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("fullScreen")}>
+ Always open full screen
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("add")}>
Always open in a new tab
</div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("replace")}>
+ Replace Tab
+ </div>
{this.props.linkDoc.linksToAnnotation ?
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("openExternal")}>
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 4dc25031d..0e03b46db 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -4,8 +4,6 @@
width: auto;
height: auto;
position: absolute;
- top: 0;
- left: 0;
z-index: 999;
.linkMenu-list {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 8ecde959f..f5a1ae8e7 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,18 +1,14 @@
-import { action, observable, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { DocumentView } from "../nodes/DocumentView";
-import { LinkEditor } from "./LinkEditor";
-import './LinkMenu.scss';
-import React = require("react");
import { Doc } from "../../../fields/Doc";
import { LinkManager } from "../../util/LinkManager";
-import { LinkMenuGroup } from "./LinkMenuGroup";
-import { faTrash } from '@fortawesome/free-solid-svg-icons';
-import { library } from "@fortawesome/fontawesome-svg-core";
import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { DocumentView } from "../nodes/DocumentView";
import { LinkDocPreview } from "../nodes/LinkDocPreview";
-
-library.add(faTrash);
+import { LinkEditor } from "./LinkEditor";
+import './LinkMenu.scss';
+import { LinkMenuGroup } from "./LinkMenuGroup";
+import React = require("react");
interface Props {
docView: DocumentView;
@@ -93,12 +89,11 @@ export class LinkMenu extends React.Component<Props> {
}
render() {
- console.log("computed", this.position.x, this.position.b);
const sourceDoc = this.props.docView.props.Document;
const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
- return <div className="linkMenu" ref={this._linkMenuRef} >
+ return <div className="linkMenu" style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.b + 15, bottom: this.props.docView.topMost ? 20 : undefined }} ref={this._linkMenuRef} >
{!this._editingLink ?
- <div className="linkMenu-list" style={{ left: this.position.x, top: this.position.b + 15 }}>
+ <div className="linkMenu-list" >
{this.renderAllGroups(groups)}
</div> :
<div className="linkMenu-listEditor" style={{ left: this.position.x, top: this.position.b + 15 }}>
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 2ae87ac13..8e09052a3 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -66,7 +66,8 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
}
render() {
- const groupItems = this.props.group.map(linkDoc => {
+ const set = new Set<Doc>(this.props.group);
+ const groupItems = Array.from(set.keys()).map(linkDoc => {
const destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
LinkManager.Instance.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
if (destination && this.props.sourceDoc) {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 21c666a4d..17549e3cf 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,27 +1,23 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { action, observable, runInAction } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
import { Cast, StrCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
+import { Hypothesis } from '../../util/HypothesisUtils';
import { LinkManager } from '../../util/LinkManager';
+import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
-import './LinkMenuItem.scss';
-import React = require("react");
-import { DocumentManager } from '../../util/DocumentManager';
-import { setupMoveUpEvents, emptyFunction, Utils, simulateMouseClick } from '../../../Utils';
-import { DocumentView } from '../nodes/DocumentView';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { DocumentView } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import { Hypothesis } from '../../util/HypothesisUtils';
-import { Id } from '../../../fields/FieldSymbols';
-import { Tooltip } from '@material-ui/core';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { undoBatch } from '../../util/UndoManager';
-import { WebField } from '../../../fields/URLField';
-library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
+import './LinkMenuItem.scss';
+import React = require("react");
interface LinkMenuItemProps {
@@ -161,7 +157,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return;
}
- if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "Default") {
+ if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "default") {
const annotationOn = this.props.destinationDoc.annotationOn as Doc;
this.props.addDocTab(annotationOn instanceof Doc ? annotationOn : this.props.destinationDoc, StrCast(linkDoc.followLinkLocation));
if (annotationOn) {
@@ -171,7 +167,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
});
}
} else {
- DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+ DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "add:right"), false);
}
linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId), this.props.destinationDoc);
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 77e2df94c..2edb73cee 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -109,12 +109,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._linkPlayDisposer?.();
this._scrubbingDisposer?.();
}
+
+ @action
componentDidMount() {
if (!this.dataDoc.markerAmount) {
this.dataDoc.markerAmount = 0;
}
- runInAction(() => this.audioState = this.path ? "paused" : undefined);
+ this.audioState = this.path ? "paused" : undefined;
this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID,
scrollLinkId => {
if (scrollLinkId) {
@@ -169,11 +171,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration);
DocListCast(this.dataDoc.links).map(l => {
const { la1, linkTime } = this.getLinkData(l);
- if (linkTime > NumCast(this.layoutDoc.currentTimecode) && linkTime < htmlEle.currentTime) {
+ if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) {
Doc.linkFollowHighlight(la1);
}
});
- this.layoutDoc.currentTimecode = htmlEle.currentTime;
+ this.layoutDoc._currentTimecode = htmlEle.currentTime;
}
}
@@ -223,7 +225,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
if (this._paused) {
this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
} else {
- this.layoutDoc.currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+ this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
}
}
}
@@ -285,7 +287,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
// creates a text document for dictation
onFile = (e: any) => {
const newDoc = Docs.Create.TextDocument("", {
- title: "", _chromeStatus: "disabled",
+ _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: "", _chromeStatus: "disabled",
x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
_width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height)
});
@@ -395,7 +397,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this.changeMarker(this._currMarker, toTimeline(e.clientX - rect.x));
return false;
},
- () => this._ele!.currentTime = this.layoutDoc.currentTimecode = toTimeline(e.clientX - rect.x),
+ () => this._ele!.currentTime = this.layoutDoc._currentTimecode = toTimeline(e.clientX - rect.x),
emptyFunction);
}
@@ -463,7 +465,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
color={"darkblue"}
height={this._waveHeight}
barWidth={0.1}
- // pos={this.layoutDoc.currentTimecode} need to correctly resize parent to make this work (not very necessary for function)
+ // pos={this.layoutDoc._currentTimecode} need to correctly resize parent to make this work (not very necessary for function)
pos={this.audioDuration}
duration={this.audioDuration}
peaks={this._buckets.length === 100 ? this._buckets : undefined}
@@ -544,7 +546,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
<div className="buttons" onClick={this._paused ? this.recordPlay : this.recordPause}>
<FontAwesomeIcon style={{ width: "100%" }} icon={this._paused ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
- <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}</div>
+ <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}</div>
</div>
:
<button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}>
@@ -562,7 +564,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
if (e.target !== this._audioRef.current) {
const wasPaused = this.audioState === "paused";
- this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration;
+ this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration;
wasPaused && this.pause();
}
@@ -624,11 +626,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
})}
{this._visible ? this.selectionContainer : null}
- <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} />
+ <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} />
{this.audio}
</div>
<div className="current-time">
- {formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}
+ {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
</div>
<div className="total-time">
{formatTime(Math.round(this.audioDuration))}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 52f6a66c8..16b2f7c2a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -58,21 +58,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@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 renderScriptDim() {
- if (this.Document.renderScript) {
- const someView = Cast(this.props.Document.someView, Doc);
- const minimap = Cast(this.props.Document.minimap, Doc);
- if (someView instanceof Doc && minimap instanceof Doc) {
- const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView._viewScale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap._width) - NumCast(minimap._width) / 2;
- const y = (NumCast(someView._panY) - NumCast(someView._height) / 2 / NumCast(someView._viewScale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2;
- const w = NumCast(someView._width) / NumCast(someView._viewScale) / NumCast(minimap.fitW) * NumCast(minimap.width);
- const h = NumCast(someView._height) / NumCast(someView._viewScale) / NumCast(minimap.fitH) * NumCast(minimap.height);
- return { x: x, y: y, width: w, height: h };
- }
- }
- return undefined;
- }
-
public static getValues(doc: Doc, time: number) {
const timecode = Math.round(time);
return ({
@@ -123,11 +108,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
setTimeout(() => doc.dataTransition = "inherit", 1010);
}
- public static setupScroll(doc: Doc, timecode: number, scrollProgressivize: boolean = false) {
+ public static setupScroll(doc: Doc, timecode: number) {
const scrollList = new List<number>();
scrollList[timecode] = NumCast(doc._scrollTop);
doc["scroll-indexed"] = scrollList;
- doc.activeFrame = ComputedField.MakeFunction("self.currentFrame");
+ doc.activeFrame = ComputedField.MakeFunction("self._currentFrame");
doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame");
}
@@ -165,7 +150,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
- public static setupZoom(doc: Doc, targDoc: Doc, zoomProgressivize: boolean = false) {
+ public static setupZoom(doc: Doc, targDoc: Doc) {
const width = new List<number>();
const height = new List<number>();
const top = new List<number>();
@@ -180,32 +165,25 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
doc["viewfinder-left-indexed"] = left;
}
- public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) {
- docs.forEach((doc, i) => {
- if (doc.appearFrame === undefined) doc.appearFrame = i;
- const curTimecode = progressivize ? i : timecode;
- const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
- const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
- const wlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
- const hlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
- const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < (doc.appearFrame ? doc.appearFrame : i) ? 0 : 1));
- const oarray = olist;
- oarray.fill(0, 0, NumCast(doc.appearFrame) - 1);
- oarray.fill(1, NumCast(doc.appearFrame), timecode);
- // oarray.fill(0, 0, NumCast(doc.appearFrame) - 1);
- // oarray.fill(1, NumCast(doc.appearFrame), timecode);\
+ public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
+ docs.forEach(doc => {
+ if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
+ const curTimecode = currTimecode;
+ const xlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]);
+ const ylist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]);
+ const wlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]);
+ const hlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]);
+ const olist = new List<number>(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1));
wlist[curTimecode] = NumCast(doc._width);
hlist[curTimecode] = NumCast(doc._height);
xlist[curTimecode] = NumCast(doc.x);
ylist[curTimecode] = NumCast(doc.y);
- doc.xArray = xlist;
- doc.yArray = ylist;
doc["x-indexed"] = xlist;
doc["y-indexed"] = ylist;
doc["w-indexed"] = wlist;
doc["h-indexed"] = hlist;
- doc["opacity-indexed"] = oarray;
- doc.activeFrame = ComputedField.MakeFunction("self.context?.currentFrame||0");
+ doc["opacity-indexed"] = olist;
+ doc.activeFrame = ComputedField.MakeFunction("self.context?._currentFrame||0");
doc._height = ComputedField.MakeInterpolated("h", "activeFrame");
doc._width = ComputedField.MakeInterpolated("w", "activeFrame");
doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
@@ -274,8 +252,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
boxShadow:
this.Opacity === 0 ? undefined : // if it's not visible, then no shadow
this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow
- this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
- this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big
+ this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc._isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ this.layoutDoc._isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big
StrCast(this.layoutDoc.boxShadow, ""),
borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding),
outline: this.Highlight ? "orange solid 2px" : "",
@@ -286,7 +264,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
zIndex: this.ZInd,
mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
display: this.ZInd === -99 ? "none" : undefined,
- pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined
+ pointerEvents: this.props.Document._isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined
}} >
{Doc.UserDoc().renderStyle !== "comic" ? (null) :
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 88eb48f51..b3b7cc4f3 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -120,6 +120,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
DataDoc={undefined}
LibraryPath={emptyPath}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
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}
@@ -149,6 +150,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
DataDoc={undefined}
LibraryPath={emptyPath}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
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}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 2408b3906..1b2070c0f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -18,14 +18,14 @@ import { DocHolderBox } from "./DocHolderBox";
import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FontIconBox } from "./FontIconBox";
-import { MenuIconBox } from "./MenuIconBox";
import { FieldView, FieldViewProps } from "./FieldView";
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox";
import { ImageBox } from "./ImageBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { PresBox } from "./PresBox";
import { SearchBox } from "../search/SearchBox";
+import { FilterBox } from "./FilterBox";
import { ColorBox } from "./ColorBox";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
import { LinkAnchorBox } from "./LinkAnchorBox";
@@ -93,7 +93,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
}
@observer
-export class DocumentContentsView extends React.Component<DocumentViewProps & {
+export class DocumentContentsView extends React.Component<DocumentViewProps & FormattedTextBoxProps & {
isSelected: (outsideReaction: boolean) => boolean,
select: (ctrl: boolean) => void,
layoutKey: string,
@@ -190,9 +190,9 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
blacklistedAttrs={[]}
renderInWrapper={false}
components={{
- FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, MenuIconBox, LabelBox, SliderBox, FieldView,
+ FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox,
+ PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox,
ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
ScreenshotBox, HTMLtag, ComparisonBox
}}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index cf8645e4c..ab6cae0ad 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -25,7 +25,7 @@ export const Flyout = higflyout.default;
interface DocumentLinksButtonProps {
View: DocumentView;
- Offset?: number[];
+ Offset?: (number | undefined)[];
AlwaysOn?: boolean;
InMenu?: boolean;
StartLink?: boolean;
@@ -36,6 +36,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
private _linkButton = React.createRef<HTMLDivElement>();
@observable public static StartLink: Doc | undefined;
+ @observable public static StartLinkView: DocumentView | undefined;
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
@@ -79,8 +80,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
//action(() => Doc.BrushDoc(this.props.View.Document));
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
} else {
DocumentLinksButton.StartLink = this.props.View.props.Document;
+ DocumentLinksButton.StartLinkView = this.props.View;
}
} else if (!this.props.InMenu) {
DocumentLinksButton.EditLink = this.props.View;
@@ -95,8 +98,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.AnnotationUri = undefined;
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
} else {
DocumentLinksButton.StartLink = this.props.View.props.Document;
+ DocumentLinksButton.StartLinkView = this.props.View;
}
//action(() => Doc.BrushDoc(this.props.View.Document));
@@ -110,6 +115,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (doubleTap && !this.props.StartLink) {
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
DocumentLinksButton.AnnotationId = undefined;
} else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
const sourceDoc = DocumentLinksButton.StartLink;
@@ -129,9 +135,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
LinkDescriptionPopup.popupY = e.screenY - 100;
LinkDescriptionPopup.descriptionPopup = true;
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
+ const rect = document.body.getBoundingClientRect();
+ if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.popupX -= 190;
+ TaskCompletionBox.popupX -= 40;
+ }
+ if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.popupY -= 40;
+ TaskCompletionBox.popupY -= 40;
+ }
setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
}
@@ -144,6 +156,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
public static finishLinkClick = undoBatch(action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView,) => {
if (startLink === endLink) {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
//!this.props.StartLink
@@ -151,8 +164,12 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
// this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks (). see note above in onLInkButtonMoved
if (endLinkView) {
- startLink._link = endLinkView._link = linkDoc;
- setTimeout(action(() => startLink._link = endLinkView._link = undefined), 0);
+ endLinkView._link = linkDoc;
+ DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView._link = linkDoc);
+ setTimeout(action(() => {
+ DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView._link = undefined);
+ endLinkView._link = undefined;
+ }), 0);
}
LinkManager.currentLink = linkDoc;
@@ -176,6 +193,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
LinkDescriptionPopup.popupY = screenY - 100;
LinkDescriptionPopup.descriptionPopup = true;
}
+
+ const rect = document.body.getBoundingClientRect();
+ if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.popupX -= 190;
+ TaskCompletionBox.popupX -= 40;
+ }
+ if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.popupY -= 40;
+ TaskCompletionBox.popupY -= 40;
+ }
+
setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
}
}
@@ -186,12 +214,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@action clearLinks() {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
}
@computed
get linkButton() {
TraceMobx();
- const links = this.props.links;
+ const links = new Set<Doc>(this.props.links);
const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
const buttonTitle = "Tap to view links";
@@ -213,7 +242,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
id={"link-icon"}
src={`/assets/${"link.png"}`} />;
- const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
+ const linkButton = <div className="documentLinksButton-cont" ref={this._linkButton} style={{
+ minWidth: 20, minHeight: 20, position: "absolute",
+ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3]
+ }}>
<div className={"documentLinksButton"} style={{
backgroundColor: this.props.InMenu ? "" : "#add8e6",
color: this.props.InMenu ? "white" : "black",
@@ -233,7 +265,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
<FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length} */}
{this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
- link : links.length}
+ link : Array.from(links).length}
</div>
{this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ?
@@ -245,7 +277,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
border: DocumentLinksButton.StartLink ? "" : "none"
}}
onPointerDown={DocumentLinksButton.StartLink ? this.completeLink : emptyFunction}
- onClick={e => DocumentLinksButton.StartLink ? DocumentLinksButton.finishLinkClick(e.screenX, e.screenY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View) : emptyFunction} /> : (null)
+ onClick={e => DocumentLinksButton.StartLink ? DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View) : emptyFunction} /> : (null)
}
{
DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
@@ -255,7 +287,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
</div >;
- return (!links.length) && !this.props.AlwaysOn ? (null) :
+ return (!Array.from(links).length) && !this.props.AlwaysOn ? (null) :
this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ?
<Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
{linkButton}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index e6b8928d4..2dd3bba91 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -133,6 +133,8 @@
bottom: 0;
width: 100%;
transform-origin: bottom left;
+ opacity: 0.1;
+ transition: opacity 0.5s;
}
}
@@ -144,4 +146,9 @@
display:inline-block;
}
}
+ > .documentView-styleWrapper {
+ > .documentView-captionWrapper {
+ opacity: 1;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d92dc0ec2..f6360fc87 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -21,7 +21,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
import { LinkManager } from '../../util/LinkManager';
import { Scripting } from '../../util/Scripting';
import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager from '../../util/SharingManager';
+import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -30,6 +30,7 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
+import { InkStrokeProperties } from '../InkStrokeProperties';
import { DocumentContentsView } from "./DocumentContentsView";
import { DocumentLinksButton } from './DocumentLinksButton';
import "./DocumentView.scss";
@@ -38,6 +39,7 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { RadialMenu } from './RadialMenu';
import { TaskCompletionBox } from './TaskCompletedBox';
import React = require("react");
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
export type DocFocusFunc = () => boolean;
@@ -45,11 +47,13 @@ export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
docFilters: () => string[];
+ searchFilterDocs: () => Doc[];
FreezeDimensions?: boolean;
NativeWidth: () => number;
NativeHeight: () => number;
Document: Doc;
DataDoc?: Doc;
+ getView?: (view: DocumentView) => any;
LayoutTemplateString?: string;
LayoutTemplate?: () => Opt<Doc>;
LibraryPath: Doc[];
@@ -69,7 +73,7 @@ export interface DocumentViewProps {
removeDocument?: (doc: Doc | Doc[]) => boolean;
moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
- setupDragLines?: () => void;
+ setupDragLines?: (snapToDraggedDoc: boolean) => void;
renderDepth: number;
ContentScaling: () => number;
PanelWidth: () => number;
@@ -104,17 +108,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _lastTap: number = 0;
private _doubleTap = false;
private _mainCont = React.createRef<HTMLDivElement>();
- private _dropDisposer?: DragManager.DragDropDisposer;
- private _showKPQuery: boolean = false;
- private _queries: string = "";
private _titleRef = React.createRef<EditableView>();
+ private _dropDisposer?: DragManager.DragDropDisposer;
private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
- private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@computed get topMost() { return this.props.renderDepth === 0; }
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
@computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@@ -128,6 +130,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClickFunc = () => this.onClickHandler;
onDoubleClickFunc = () => this.onDoubleClickHandler;
+ constructor(props: any) {
+ super(props);
+ props.getView?.(this);
+ }
+
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -174,10 +181,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
- // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
(effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
- // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
@@ -191,7 +198,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._mainCont.current && (this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
// this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
- if (!this.props.dontRegisterView) {
+ if (!BoolCast(this.rootDoc.dontRegisterView, this.props.dontRegisterView)) {
DocumentManager.Instance.DocumentViews.push(this);
}
}
@@ -285,13 +292,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
+ _timeout: NodeJS.Timeout | undefined;
+
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
let stopPropagate = true;
let preventDefault = true;
- !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
- if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ !this.props.Document._isBackground && this.props.bringToFront(this.props.Document);
+ if (this._doubleTap && ((this.props.renderDepth && this.props.Document.type !== DocumentType.FONTICON) || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ if (this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = undefined;
+ }
if (!(e.nativeEvent as any).formattedHandled) {
if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
const func = () => this.onDoubleClickHandler.script.run({
@@ -301,40 +314,53 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
shiftKey: e.shiftKey
}, console.log);
func();
- } else {
- UndoManager.RunInBatch(() => {
- let fullScreenDoc = this.props.Document;
- if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
- fullScreenDoc = Doc.MakeAlias(this.props.Document);
- fullScreenDoc.layoutKey = "layout_fullScreen";
- }
- this.props.addDocTab(fullScreenDoc, "inTab");
- }, "double tap");
- SelectionManager.DeselectAll();
+ } else if (!Doc.IsSystem(this.props.Document)) {
+ if (this.props.Document.type === DocumentType.INK) {
+ InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = true);
+ } else {
+ UndoManager.RunInBatch(() => {
+ let fullScreenDoc = this.props.Document;
+ if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
+ fullScreenDoc = Doc.MakeAlias(this.props.Document);
+ fullScreenDoc.layoutKey = "layout_fullScreen";
+ }
+ this.props.addDocTab(fullScreenDoc, "add");
+ }, "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
+ const shiftKey = e.shiftKey;
const func = () => this.onClickHandler.script.run({
this: this.layoutDoc,
self: this.rootDoc,
scriptContext: this.props.scriptContext,
thisContainer: this.props.ContainingCollectionDoc,
documentView: this,
- shiftKey: e.shiftKey
+ shiftKey
}, console.log);
- if (!Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-undo"] as Doc) && !Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-redo"] as Doc)) {
- UndoManager.RunInBatch(func, "on click");
- } else func();
+ const clickFunc = () => {
+ if (!Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-undo"] as Doc) &&
+ !Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-redo"] as Doc) &&
+ !this.onClickHandler.script.originalScript.includes("selectMainMenu")) {
+ UndoManager.RunInBatch(func, "on click");
+ } else func();
+ };
+ if (this.onDoubleClickHandler) {
+ this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 500);
+ } else clickFunc();
} 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
- this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
+ this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
} else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
- SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
+ this.select(e.ctrlKey || e.shiftKey);
+ //SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
}
preventDefault = false;
}
@@ -564,8 +590,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch @action
deleteClicked = (): void => {
- if (Doc.UserDoc().activeWorkspace === this.props.Document) {
- alert("Can't delete the active workspace");
+ if (CurrentUserUtils.ActiveDashboard === this.props.Document) {
+ alert("Can't delete the active dashboard");
} else {
const selected = SelectionManager.SelectedDocuments().slice();
SelectionManager.DeselectAll();
@@ -601,8 +627,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch @action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (this.props.Document === Doc.UserDoc().activeWorkspace) {
- alert("linking to document tabs not yet supported. Drop link on document content.");
+ if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
+ if ((e.target as any)?.closest?.("*.lm_content")) {
+ alert("You can't perform this move most likely because you don't have permission to modify the destination.");
+ }
+ else alert("linking to document tabs not yet supported. Drop link on document content.");
return;
}
const makeLink = action((linkDoc: Doc) => {
@@ -617,15 +646,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
LinkDescriptionPopup.popupY = de.y;
LinkDescriptionPopup.descriptionPopup = true;
+ const rect = document.body.getBoundingClientRect();
+ if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.popupX -= 190;
+ TaskCompletionBox.popupX -= 40;
+ }
+ if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.popupY -= 40;
+ TaskCompletionBox.popupY -= 40;
+ }
+
setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
});
if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
e.stopPropagation();
- de.complete.annoDragData.linkedToDoc = true;
-
- const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
- linkDoc && makeLink(linkDoc);
+ de.complete.annoDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+ de.complete.annoDragData.linkDocument && makeLink(de.complete.annoDragData.linkDocument);
}
if (de.complete.linkDragData) {
e.stopPropagation();
@@ -666,12 +703,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
toggleBackground = () => {
- this.Document.isBackground = (this.Document.isBackground ? undefined : true);
- this.Document._overflow = this.Document.isBackground ? "visible" : undefined;
- if (this.Document.isBackground) {
+ this.Document._isBackground = (this.Document._isBackground ? undefined : true);
+ this.Document._overflow = this.Document._isBackground ? "visible" : undefined;
+ 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]();
+ const wid = this.Document[WidthSym](); // change the nativewidth and height if the background is to be a collection that aggregates stuff that is added to it.
+ const hgt = this.Document[HeightSym]();
+ this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = wid;
+ this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = hgt;
}
}
@@ -684,7 +723,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@action
- onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
+ onContextMenu = (e: React.MouseEvent | Touch) => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (!(e instanceof Touch)) {
if (e.button === 0 && !e.ctrlKey) {
@@ -703,38 +742,41 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
const cm = ContextMenu.Instance;
- if (!cm) return;
+ if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
this.props.contextMenuItems?.().forEach(item =>
- cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+ item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
const appearance = cm.findByDescription("UI Controls...");
const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
- templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
+ templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
//DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
!appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ optionItems.push({ description: "Bring to Front", event: () => this.props.bringToFront(this.rootDoc, false), icon: "expand-arrows-alt" });
+ optionItems.push({ description: "Send to Back", event: () => this.props.bringToFront(this.rootDoc, true), icon: "expand-arrows-alt" });
+ !this.props.treeViewDoc && this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
-
- 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" });
- onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
- onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
- !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "link" });
- onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
- onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
- !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+ if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+ 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" });
+ onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
+ onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
+ !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
+ onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
+ onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+ }
const funcs: ContextMenuProps[] = [];
if (this.layoutDoc.onDragStart) {
@@ -760,18 +802,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
- Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()) && moreItems.push({ description: "Toggle Always Show Link End", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" });
+ Doc.AreProtosEqual(this.props.Document, Cast(Doc.UserDoc().myUserDoc, Doc, null)) && moreItems.push({ description: "Toggle Alternate Button Bar", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" });
}
- moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
+ const collectionAcl = GetEffectiveAcl(this.props.ContainingCollectionDoc?.[DataSym]);
+ if ((collectionAcl === AclAdmin || collectionAcl === AclEdit) && this.props.removeDocument) {
+ moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
+ }
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
@@ -811,13 +856,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
}
childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
- @computed.struct get linkOffset() { return [-15, 0]; }
+ @computed.struct get linkOffset() { return this.topMost ? [0, undefined, undefined, 10] : [-15, undefined, undefined, undefined]; }
@computed get contents() {
const pos = this.props.relative ? "relative " : "absolute";
TraceMobx();
return (<div className="documentView-contentsView" style={{ borderRadius: "inherit", width: "100%", height: "100%" }}>
<DocumentContentsView key={1}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
NativeWidth={this.NativeWidth}
@@ -879,31 +925,34 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
- @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.Document); }
+ @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
@computed.struct get allLinks() { return DocListCast(this.Document.links); }
@computed.struct get allAnchors() {
TraceMobx();
if (this.props.LayoutTemplateString?.includes("LinkAnchorBox")) return null;
- return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots
+ if ((this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots
this.layoutDoc.presBox || // presentationbox nodes
this.rootDoc.type === DocumentType.LINK ||
- this.props.dontRegisterView ? (null) : // view that are not registered
- DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-anchorCont" key={i + 1}>
- <DocumentView {...this.props}
- Document={d}
- ContainingCollectionView={this.props.ContainingCollectionView}
- 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}
- ContentScaling={returnOne}
- dontRegisterView={false}
- forcedBackgroundColor={returnTransparent}
- removeDocument={this.hideLinkAnchor}
- pointerEvents={false}
- LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
- </div >);
+ this.props.dontRegisterView) {// view that are not registered
+ return (null);
+ }
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink(d));
+ return filtered.map((d, i) =>
+ <div className="documentView-anchorCont" key={i + 1}>
+ <DocumentView {...this.props}
+ Document={d}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ 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}
+ ContentScaling={returnOne}
+ dontRegisterView={false}
+ forcedBackgroundColor={returnTransparent}
+ removeDocument={this.hideLinkAnchor}
+ pointerEvents={false}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
+ </div >);
}
@computed get innards() {
TraceMobx();
@@ -925,6 +974,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const captionView = (!showCaption ? (null) :
<div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
<DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
+ yMargin={10}
+ xMargin={10}
hideOnLeave={true}
LayoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`}
ContentScaling={returnOne}
@@ -955,7 +1006,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@computed get ignorePointerEvents() {
return this.props.pointerEvents === false ||
- (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
+ (this.Document._isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) ||
(this.Document.type === DocumentType.INK && Doc.GetSelectedTool() !== InkTool.None);
}
@undoBatch
@@ -977,11 +1028,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
renderLock() {
- return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
+ return (this.Document._isBackground !== undefined || this.isSelected(false)) &&
((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
this.props.renderDepth > 0 && !this.props.treeViewDoc ?
<div className="documentView-lock" onClick={this.toggleBackground}>
- <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
+ <FontAwesomeIcon icon={this.Document._isBackground ? "unlock" : "lock"} style={{ color: this.Document._isBackground ? "red" : undefined }} size="lg" />
</div>
: (null);
}
@@ -997,7 +1048,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
const borderRounding = this.layoutDoc.borderRounding;
const localScale = fullDegree;
- const highlightColors = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ?
+ const highlightColors = CurrentUserUtils.ActiveDashboard?.darkScheme ?
["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index 92ca276cb..0d1e063af 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -17,7 +17,7 @@ export interface RectangleTemplate {
}
@observer
-export default class FaceRectangles extends React.Component<FaceRectanglesProps> {
+export class FaceRectangles extends React.Component<FaceRectanglesProps> {
render() {
const faces = DocListCast(this.props.document.faces);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 9d61ec6d1..fceeced36 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,6 +29,7 @@ export interface FieldViewProps {
dropAction: dropActionType;
backgroundHalo?: () => boolean;
docFilters: () => string[];
+ searchFilterDocs: () => Doc[];
isSelected: (outsideReaction?: boolean) => boolean;
select: (isCtrlPressed: boolean) => void;
rootSelected: (outsideReaction?: boolean) => boolean;
@@ -58,9 +59,6 @@ export interface FieldViewProps {
ChromeHeight?: () => number;
childLayoutTemplate?: () => Opt<Doc>;
- highlighting?: string[];
- lines?: string[];
- doc?: 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;
@@ -133,7 +131,7 @@ export class FieldView extends React.Component<FieldViewProps> {
// );
}
else if (field instanceof List) {
- return <div> {field.map(f => Field.toString(f)).join(", ")} </div>;
+ return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : "[]"} </div>;
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
else if (field instanceof WebField) {
diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss
new file mode 100644
index 000000000..b39793f01
--- /dev/null
+++ b/src/client/views/nodes/FilterBox.scss
@@ -0,0 +1,54 @@
+
+
+.filterBox-flyout {
+ width: 400px;
+ display: block;
+ text-align: left;
+ .filterBox-flyout-facet {
+ background-color: lightgray;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ }
+}
+.filterBox-treeView {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ top: 0;
+ border-left: solid 1px;
+ z-index: 1;
+
+ .filterBox-addfacet {
+ display: inline-block;
+ width: 200px;
+ height: 30px;
+ background: darkGray;
+ text-align: left;
+
+ .filterBox-addFacetButton {
+ display: flex;
+ margin: auto;
+
+ .filterBox-span {
+ margin-right: 15px;
+ }
+ }
+
+ >div,
+ >div>div {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .filterBox-tree {
+ display: inline-block;
+ width: 100%;
+ height: calc(100% - 30px);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
new file mode 100644
index 000000000..790901a29
--- /dev/null
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -0,0 +1,215 @@
+import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { List } from "../../../fields/List";
+import { RichTextField } from "../../../fields/RichTextField";
+import { listSpec, makeInterface } from "../../../fields/Schema";
+import { ComputedField, ScriptField } from "../../../fields/ScriptField";
+import { Cast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionTreeView } from "../collections/CollectionTreeView";
+import { ViewBoxBaseComponent } from "../DocComponent";
+import { SearchBox } from "../search/SearchBox";
+import { FieldView, FieldViewProps } from './FieldView';
+import './FilterBox.scss';
+import { Scripting } from "../../util/Scripting";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+type FilterBoxDocument = makeInterface<[typeof documentSchema]>;
+const FilterBoxDocument = makeInterface(documentSchema);
+
+@observer
+export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDocument>(FilterBoxDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); }
+
+ @computed get allDocs() {
+ const allDocs = new Set<Doc>();
+ if (CollectionDockingView.Instance) {
+ const activeTabs = DocListCast(CollectionDockingView.Instance.props.Document.data);
+ SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc));
+ setTimeout(() => CollectionDockingView.Instance.props.Document.allDocuments = new List<Doc>(Array.from(allDocs)));
+ }
+ return allDocs;
+ }
+
+ @computed get _allFacets() {
+ const noviceReqFields = ["author", "tags", "text", "type"];
+ const noviceLayoutFields: string[] = [];//["_curPage"];
+ const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
+
+ const keys = new Set<string>(noviceFields);
+ this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
+ return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort();
+ }
+ /**
+ * Responds to clicking the check box in the flyout menu
+ */
+ facetClick = (facetHeader: string) => {
+ const targetDoc = CollectionDockingView.Instance.props.Document;
+ const found = DocListCast(this.dataDoc[this.props.fieldKey]).findIndex(doc => doc.title === facetHeader);
+ if (found !== -1) {
+ (this.dataDoc[this.props.fieldKey] as List<Doc>).splice(found, 1);
+ const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+ docFilter.splice(index, 3);
+ }
+ }
+ const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
+ if (docRangeFilters) {
+ let index: number;
+ while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
+ docRangeFilters.splice(index, 3);
+ }
+ }
+ } else {
+ const allCollectionDocs = DocListCast((targetDoc.data as any)[0].data);
+ var rtfields = 0;
+ const facetValues = Array.from(allCollectionDocs.reduce((set, child) => {
+ const field = child[facetHeader] as Field;
+ const fieldStr = Field.toString(field);
+ if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++;
+ return set.add(fieldStr);
+ }, new Set<string>()));
+
+ let nonNumbers = 0;
+ let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+ facetValues.map(val => {
+ const num = Number(val);
+ if (Number.isNaN(num)) {
+ nonNumbers++;
+ } else {
+ minVal = Math.min(num, minVal);
+ maxVal = Math.max(num, maxVal);
+ }
+ });
+ let newFacet: Opt<Doc>;
+ if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) {
+ newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
+ Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+ newFacet._textBoxPadding = 4;
+ const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
+ newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
+ } else if (facetHeader !== "tags" && nonNumbers / facetValues.length < .1) {
+ newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true });
+ const newFacetField = Doc.LayoutFieldKey(newFacet);
+ const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader);
+ Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+ const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05);
+ const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05);
+ newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
+ newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
+ Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal;
+ Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
+ const scriptText = `setDocFilterRange(this?.target, "${facetHeader}", range)`;
+ newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+ } else {
+ newFacet = new Doc();
+ newFacet.sytem = true;
+ newFacet.title = facetHeader;
+ newFacet.treeViewOpen = true;
+ newFacet.type = DocumentType.COL;
+ const capturedVariables = { layoutDoc: targetDoc, dataDoc: (targetDoc.data as any)[0][DataSym] };
+ newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables);
+ }
+ newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
+ }
+ }
+ filterBackground = () => "rgba(105, 105, 105, 0.432)";
+ get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
+ @computed get scriptField() {
+ const scriptText = "setDocFilter(this?.target, heading, this.title, checked)";
+ const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ return script ? () => script : undefined;
+ }
+
+ render() {
+ const facetCollection = this.props.Document.proto as Doc;
+ const flyout = <div className="filterBox-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
+ {this._allFacets.map(facet => <label className="filterBox-flyout-facet" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+ <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} />
+ <span className="checkmark" />
+ {facet}
+ </label>)}
+ </div>;
+
+ return this.props.dontRegisterView ? (null) : <div className="filterBox-treeView" style={{ width: "100%" }}>
+ <div className="filterBox-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+ <div className="filterBox-addFacetButton">
+ <FontAwesomeIcon icon={"edit"} size={"lg"} />
+ <span className="filterBox-span">Choose Facets</span>
+ </div>
+ </Flyout>
+ </div>
+ <div className="filterBox-tree" key="tree">
+ <CollectionTreeView
+ PanelPosition={""}
+ Document={facetCollection}
+ DataDoc={Doc.GetProto(facetCollection)}
+ fieldKey={`${this.props.fieldKey}`}
+ CollectionView={undefined}
+ docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ LibraryPath={emptyPath}
+ rootSelected={this.props.rootSelected}
+ renderDepth={1}
+ dropAction={this.props.dropAction}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ isSelected={returnFalse}
+ select={returnFalse}
+ bringToFront={emptyFunction}
+ active={this.props.active}
+ whenActiveChanged={returnFalse}
+ treeViewHideTitle={true}
+ ContentScaling={returnOne}
+ focus={returnFalse}
+ treeViewHideHeaderFields={true}
+ onCheckedClick={this.scriptField}
+ ignoreFields={this.ignoreFields}
+ annotationsKey={""}
+ dontRegisterView={true}
+ backgroundColor={this.filterBackground}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ addDocument={returnFalse} />
+ </div>
+ </div>;
+ }
+}
+
+Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) {
+ const allCollectionDocs = DocListCast(CollectionDockingView.Instance?.props.Document.allDocuments);
+ const set = new Set<string>();
+ if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key)));
+ else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
+ let facetValues = Array.from(set).filter(v => v);
+
+ let nonNumbers = 0;
+ facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
+ const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
+ const doc = new Doc();
+ doc.system = true;
+ doc.title = facetValue.toString();
+ doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue });
+ return doc;
+ });
+ return new List<Doc>(facetValueDocSet);
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 75bc90d7a..33ac85a0e 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -34,12 +34,14 @@
}
}
+.menuButton-circle,
.menuButton-round {
border-radius: 100%;
background-color: black;
+ padding: 0;
.fontIconBox-label {
- margin-left: -10px; // button padding is 10px;
+ //margin-left: -10px; // button padding is 10px;
bottom: 0;
position: absolute;
}
@@ -52,7 +54,6 @@
.menuButton-square {
padding-top: 3px;
padding-bottom: 3px;
- padding-left: 5px;
.fontIconBox-label {
border-radius: 0px;
@@ -62,9 +63,11 @@
}
.menuButton,
+.menuButton-circle,
.menuButton-round,
.menuButton-square {
- width: 100%;
+ margin-left: -5%;
+ width: 110%;
height: 100%;
pointer-events: all;
touch-action: none;
@@ -72,11 +75,7 @@
.menuButton-wrap {
touch-action: none;
border-radius: 8px;
-
- // &:hover {
- // background: rgb(61, 61, 61);
- // cursor: pointer;
- // }
+ width: 100%;
}
.menuButton-icon-square {
@@ -89,4 +88,13 @@
width: 95% !important;
height: 95%;
}
+}
+.menuButton-round {
+ width: 100%;
+ svg {
+ width: 50% !important;
+ height: 50%;
+ position: relative;
+ bottom: 2px;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index fd71876b0..a34bf64b0 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -39,7 +39,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
showTemplate = (): void => {
const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, "onRight");
+ dragFactory && this.props.addDocTab(dragFactory, "add:right");
}
dragAsTemplate = (): void => {
this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
@@ -63,15 +63,18 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
const color = StrCast(this.layoutDoc.color, this._foregroundColor);
const backgroundColor = StrCast(this.layoutDoc._backgroundColor, StrCast(this.rootDoc.backgroundColor, this.props.backgroundColor?.(this.rootDoc, this.props.renderDepth)));
- const shape = StrCast(this.layoutDoc.iconShape, "round");
-
+ const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle");
+ const icon = StrCast(this.dataDoc.icon, "user") as any;
+ const presSize = shape === 'round' ? 25 : 30;
+ const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`}
+ style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />;
const button = <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu}
style={{
boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined,
backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "",
}}>
<div className="menuButton-wrap">
- {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color}
+ {icon === 'pres-trail' ? presTrailsIcon : <FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={icon} color={color}
size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />}
{!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>}
{this.props.Document.watchedDocuments ? <FontIconBadge collection={Cast(this.props.Document.watchedDocuments, Doc, null)} /> : (null)}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 216c5f39e..688bac725 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,8 +1,5 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, observable, runInAction, reaction, IReactionDisposer } from 'mobx';
import { observer } from "mobx-react";
import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
@@ -14,7 +11,8 @@ 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 { emptyFunction, returnOne, returnZero, Utils } from '../../../Utils';
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Docs } from '../../documents/Documents';
import { Networking } from '../../Network';
@@ -24,22 +22,16 @@ import { ContextMenu } from "../../views/ContextMenu";
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import FaceRectangles from './FaceRectangles';
+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');
-library.add(faImage, faEye as any, faPaintBrush, faBrain);
-library.add(faFileAudio, faAsterisk);
-
-
export const pageSchema = createSchema({
- curPage: "number",
+ _curPage: "number",
fitWidth: "boolean",
googlePhotosUrl: "string",
googlePhotosTags: "string"
@@ -70,15 +62,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _dropDisposer?: DragManager.DragDropDisposer;
+ private _pathDisposer?: IReactionDisposer;
@observable private _audioState = 0;
@observable static _showControls: boolean;
@observable uploadIcon = uploadIcons.idle;
protected createDropTarget = (ele: HTMLDivElement) => {
- this._dropDisposer && this._dropDisposer();
+ this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
}
+ componentDidMount() {
+ this._pathDisposer = reaction(() => this.paths.length && this.resize(this.paths[0]),
+ () => true,
+ { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._pathDisposer?.();
+ }
+
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
@@ -111,23 +113,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
}).then(function (stream) {
gumStream = stream;
recorder = new MediaRecorder(stream);
- recorder.ondataavailable = async function (e: any) {
- const formData = new FormData();
- formData.append("file", e.data);
- const res = await fetch(Utils.prepend("/uploadFormData"), {
- method: 'POST',
- body: formData
- });
- const files = await res.json();
- const url = Utils.prepend(files[0].path);
- // upload to server with known URL
- const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 });
- audioDoc.treeViewExpandedView = "layout";
- const audioAnnos = Cast(this.dataDoc[this.fieldKey + "-audioAnnotations"], listSpec(Doc));
- if (audioAnnos === undefined) {
- this.dataDoc[this.fieldKey + "-audioAnnotations"] = new List([audioDoc]);
- } else {
- audioAnnos.push(audioDoc);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer(e.data);
+ if (!(result instanceof Error)) {
+ const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 });
+ audioDoc.treeViewExpandedView = "layout";
+ const audioAnnos = Cast(self.dataDoc[self.fieldKey + "-audioAnnotations"], listSpec(Doc));
+ if (audioAnnos === undefined) {
+ self.dataDoc[self.fieldKey + "-audioAnnotations"] = new List([audioDoc]);
+ } else {
+ audioAnnos.push(audioDoc);
+ }
}
};
runInAction(() => self._audioState = 2);
@@ -158,7 +154,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (field) {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
- funcs.push({ description: "Make Background", event: () => { this.layoutDoc.isBackground = true; this.props.bringToFront?.(this.rootDoc); }, icon: "expand-arrows-alt" });
+ funcs.push({ description: "Make Background", event: () => { this.layoutDoc._isBackground = true; this.props.bringToFront?.(this.rootDoc); }, icon: "expand-arrows-alt" });
if (!Doc.UserDoc().noviceMode) {
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" });
@@ -255,29 +251,29 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
resize = (imgPath: string) => {
const basePath = imgPath.replace(/_[oms]./, "");
+ const curPath = this.dataDoc[this.fieldKey + "-path"];
const cachedNativeSize = {
- 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,
+ width: basePath === curPath || !curPath ? NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]) : 0,
+ height: basePath === curPath || !curPath ? NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]) : 0,
};
const docAspect = this.layoutDoc[HeightSym]() / this.layoutDoc[WidthSym]();
const cachedAspect = cachedNativeSize.height / cachedNativeSize.width;
if (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(NumCast(this.layoutDoc._width) / NumCast(this.layoutDoc._height) - cachedNativeSize.width / cachedNativeSize.height) > 0.05) {
if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) {
- requestImageSize(imgPath).then(action((inquiredSize: any) => {
- const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180;
- const rotatedNativeSize = { width: inquiredSize.width, height: inquiredSize.height };
- if (inquiredSize.orientation === 6 || rotation === 90 || rotation === 270) {
- rotatedNativeSize.width = inquiredSize.height;
- rotatedNativeSize.height = inquiredSize.width;
- }
- const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
- if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
- 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"] = basePath;
- }
- })).catch(console.log);
+ const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180;
+ const orientation = NumCast(this.dataDoc[this.fieldKey + "-nativeOrientation"]);
+ if (orientation === 6 || rotation === 90 || rotation === 270) {
+ this.layoutDoc._nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ this.layoutDoc._nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+ } else {
+ this.layoutDoc._nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+ this.layoutDoc._nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ }
+ const rotatedAspect = NumCast(this.layoutDoc._nativeHeight) / NumCast(this.layoutDoc._nativeWidth);
+ if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
+ this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect;
+ this.dataDoc[this.fieldKey + "-path"] = basePath;
+ }
} else if (Math.abs(1 - docAspect / cachedAspect) > 0.1) {
this.layoutDoc._width = this.layoutDoc[WidthSym]() || cachedNativeSize.width;
this.layoutDoc._height = this.layoutDoc[WidthSym]() * cachedAspect;
@@ -398,8 +394,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
const { nativeWidth, nativeHeight } = this.nativeSize;
const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
const aspect = (rotation % 180) ? nativeHeight / nativeWidth : 1;
- const shift = (rotation % 180) ? (nativeHeight - nativeWidth) * (1 - 1 / aspect) : 0;
- this.resize(srcpath);
let transformOrigin = "center center";
let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
if (rotation === 90 || rotation === -270) {
@@ -437,7 +431,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
>
<FontAwesomeIcon className="imageBox-audioFont"
style={{ color: [DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
- icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
+ icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
</div>}
{this.considerDownloadIcon}
{this.considerGooglePhotosLink()}
@@ -465,7 +459,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
- pointerEvents: this.layoutDoc.isBackground ? "none" : undefined,
+ pointerEvents: this.layoutDoc._isBackground ? "none" : undefined,
borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
}} >
<CollectionFreeFormView {...this.props}
@@ -489,6 +483,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
ScreenToLocalTransform={this.screenToLocalTransform}
renderDepth={this.props.renderDepth + 1}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
{this.contentFunc}
</CollectionFreeFormView>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index b732f5f83..c5ff42a1a 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -230,7 +230,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
openItems.push({
description: "Default Perspective", event: () => {
this.props.addDocTab(this.props.Document, "close");
- this.props.addDocTab(this.fieldDocToLayout, "onRight");
+ this.props.addDocTab(this.fieldDocToLayout, "add:right");
}, 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 4568a6b16..74d10d087 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -46,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
if (value instanceof Doc) {
e.stopPropagation();
e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
}
}
@@ -56,7 +56,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
Document: this.props.doc,
DataDoc: this.props.doc,
LibraryPath: [],
- docFilters:returnEmptyFilter,
+ docFilters: returnEmptyFilter,
+ searchFilterDocs: returnEmptyDoclist,
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
fieldKey: this.props.keyName,
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index b605df262..109a02df4 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -8,6 +8,7 @@
}
.labelBox-mainButton {
+ max-width: 100%;
width: fit-content;
height: max-content;
border-radius: inherit;
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 42ef2958e..62ee9513c 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -22,6 +22,11 @@
padding-left: 2px;
padding-top: 1px;
}
+ .linkAnchorBox-button {
+ pointer-events: all;
+ position: relative;
+ display: inline-block;
+ }
}
.linkAnchorBox-cont-small {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 50b2af0d7..10b6aa02e 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -73,7 +73,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
this._timeout = setTimeout(action(() => {
- DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, e.altKey ? "inTab" : "onRight")), false);
+ DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "add:right")), false);
this._editing = false;
}), 300 - (Date.now() - this._lastTap));
}
@@ -87,14 +87,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
}
openLinkDocOnRight = (e: React.MouseEvent) => {
- this.props.addDocTab(this.rootDoc, "onRight");
+ this.props.addDocTab(this.rootDoc, "add:right");
}
openLinkTargetOnRight = (e: React.MouseEvent) => {
const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
alias.isLinkButton = undefined;
- alias.isBackground = undefined;
+ alias._isBackground = undefined;
alias.layoutKey = "layout";
- this.props.addDocTab(alias, "onRight");
+ this.props.addDocTab(alias, "add:right");
}
@action
openLinkEditor = action((e: React.MouseEvent) => {
@@ -148,7 +148,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
}} >
{!this._editing && !this._forceOpen ? (null) :
<Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => this._isOpen = true} onClose={action(() => this._isOpen = this._forceOpen = this._editing = false)}>
- <span className="parentDocumentSelector-button" >
+ <span className="linkAnchorBox-button" >
<FontAwesomeIcon icon={"eye"} size={"lg"} />
</span>
</Flyout>}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 532e7dc15..a067f23af 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -26,6 +26,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
NativeWidth={returnZero}
ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)}
annotationsKey={""}
+ dontRegisterView={true}
+ renderDepth={this.props.renderDepth + 1}
CollectionView={undefined}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index c4481b213..dddefc17f 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import wiki from "wikijs";
import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Cast, FieldValue, NumCast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, returnEmptyDoclist } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { Transform } from "../../util/Transform";
@@ -22,6 +22,7 @@ interface Props {
}
@observer
export class LinkDocPreview extends React.Component<Props> {
+ static TargetDoc: Doc | undefined;
@observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>;
@observable _targetDoc: Opt<Doc>;
@observable _toolTipText = "";
@@ -40,14 +41,16 @@ export class LinkDocPreview extends React.Component<Props> {
async followDefault() {
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
- this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "onRight"), false) : null;
+ this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "add:right"), false) : null;
}
+ componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; }
componentDidUpdate() { this.updatePreview(); }
componentDidMount() { this.updatePreview(); }
async updatePreview() {
const linkDoc = this.props.linkDoc;
const linkSrc = this.props.linkSrc;
+ LinkDocPreview.TargetDoc = undefined;
if (this.props.href) {
if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
@@ -59,7 +62,7 @@ export class LinkDocPreview extends React.Component<Props> {
const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
runInAction(() => {
this._toolTipText = "";
- this._targetDoc = target;
+ LinkDocPreview.TargetDoc = this._targetDoc = target;
if (anchor !== this._targetDoc && anchor && this._targetDoc) {
this._targetDoc._scrollY = NumCast(anchor?.y);
}
@@ -69,13 +72,13 @@ export class LinkDocPreview extends React.Component<Props> {
pointerDown = (e: React.PointerEvent) => {
if (this.props.linkDoc && this.props.linkSrc) {
DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc,
- (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
} else if (this.props.href) {
- this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight");
+ this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right");
}
}
- width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
- height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
+ width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)) - 16;
+ height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)) - 16
@computed get targetDocView() {
return !this._targetDoc ?
<div style={{
@@ -101,11 +104,12 @@ export class LinkDocPreview extends React.Component<Props> {
pinToPres={returnFalse}
dontRegisterView={true}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={0}
- PanelWidth={() => this.width() - 16} //Math.min(350, NumCast(target._width, 350))}
- PanelHeight={() => this.height() - 16} //Math.min(250, NumCast(target._height, 250))}
+ PanelWidth={this.width} //Math.min(350, NumCast(target._width, 350))}
+ PanelHeight={this.height} //Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
@@ -119,7 +123,7 @@ export class LinkDocPreview extends React.Component<Props> {
return <div className="linkDocPreview"
style={{
position: "absolute", left: this.props.location[0],
- top: this.props.location[1], width: this.width(), height: this.height(),
+ top: this.props.location[1], width: this.width() + 16, height: this.height() + 16,
zIndex: 1000,
border: "8px solid white", borderRadius: "7px",
boxShadow: "3px 3px 1.5px grey",
diff --git a/src/client/views/nodes/MenuIconBox.scss b/src/client/views/nodes/MenuIconBox.scss
deleted file mode 100644
index 1b72f5a8f..000000000
--- a/src/client/views/nodes/MenuIconBox.scss
+++ /dev/null
@@ -1,49 +0,0 @@
-.menuButton {
- //padding: 7px;
- padding-left: 7px;
- width: 100%;
- width: 60px;
- height: 70px;
-
- .menuButton-wrap {
- width: 45px;
- /* padding: 5px; */
- touch-action: none;
- background: black;
- transform-origin: top left;
- /* margin-bottom: 5px; */
- margin-top: 5px;
- margin-right: 25px;
- border-radius: 8px;
-
- &:hover {
- background: rgb(61, 61, 61);
- cursor: pointer;
- }
- }
-
- .menuButton-label {
- color: white;
- margin-right: 4px;
- border-radius: 8px;
- width: 42px;
- position: relative;
- text-align: center;
- font-size: 8px;
- margin-top: 1px;
- letter-spacing: normal;
- padding: 3px;
- background-color: inherit;
- }
-
- .menuButton-icon {
- width: auto;
- height: 35px;
- padding: 5px;
- }
-
- svg {
- width: 95% !important;
- height: 95%;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/MenuIconBox.tsx b/src/client/views/nodes/MenuIconBox.tsx
deleted file mode 100644
index 5ed8a9b78..000000000
--- a/src/client/views/nodes/MenuIconBox.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { createSchema, makeInterface } from '../../../fields/Schema';
-import { StrCast } from '../../../fields/Types';
-import { DocComponent } from '../DocComponent';
-import { FieldView, FieldViewProps } from './FieldView';
-import './MenuIconBox.scss';
-const MenuIconSchema = createSchema({
- icon: "string"
-});
-
-type MenuIconDocument = makeInterface<[typeof MenuIconSchema]>;
-const MenuIconDocument = makeInterface(MenuIconSchema);
-@observer
-export class MenuIconBox extends DocComponent<FieldViewProps, MenuIconDocument>(MenuIconDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MenuIconBox, fieldKey); }
- _ref: React.RefObject<HTMLButtonElement> = React.createRef();
-
- render() {
-
- const color = this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) === "lightgrey" ? "black" : "white";
- const menuBTN = <div className="menuButton" style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }}>
- <div className="menuButton-wrap"
- style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }} >
- <FontAwesomeIcon className="menuButton-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={color} size="lg" />
- <div className="menuButton-label" style={{ color: color }}> {this.dataDoc.title} </div>
- </div>
- </div>;
-
- return menuBTN;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 255a1b2d0..a2b406c3f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -21,6 +21,7 @@ import { KeyCodes } from '../../util/KeyCodes';
import "./PDFBox.scss";
import React = require("react");
import { documentSchema } from '../../../fields/documentSchemas';
+import { CollectionViewType } from '../collections/CollectionView';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -99,20 +100,28 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); };
public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); };
public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); };
- public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); };
- public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); };
- public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
+ public backPage = () => { this.Document._curPage = (this.Document._curPage || 1) - 1; return true; };
+ public forwardPage = () => { this.Document._curPage = (this.Document._curPage || 1) + 1; return true; };
+ public gotoPage = (p: number) => { this.Document._curPage = p; };
@undoBatch
onKeyDown = action((e: KeyboardEvent) => {
+ let processed = false;
if (e.key === "f" && e.ctrlKey) {
this._searching = true;
setTimeout(() => this._searchRef.current && this._searchRef.current.focus(), 100);
+ processed = true;
+ }
+ if (e.key === "PageDown") processed = this.forwardPage();
+ if (e.key === "PageUp") processed = this.backPage();
+ if (e.target instanceof HTMLInputElement || this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) {
+ if (e.key === "ArrowDown" || e.key === "ArrowRight") processed = this.forwardPage();
+ if (e.key === "ArrowUp" || e.key === "ArrowLeft") processed = this.backPage();
+ }
+ if (processed) {
e.stopImmediatePropagation();
e.preventDefault();
}
- if (e.key === "PageDown" || e.key === "ArrowDown" || e.key === "ArrowRight") this.forwardPage();
- if (e.key === "PageUp" || e.key === "ArrowUp" || e.key === "ArrowLeft") this.backPage();
});
@undoBatch
@@ -150,7 +159,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
</button>
</>;
const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
- const curPage = this.Document.curPage || 1;
+ const curPage = this.Document._curPage || 1;
return !this.active() ? (null) :
(<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true}
onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}>
@@ -177,7 +186,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<div className="pdfBox-pageNums">
<input value={curPage}
- onChange={e => this.gotoPage(Number(e.currentTarget.value))}
+ onChange={e => this.Document._curPage = Number(e.currentTarget.value)}
style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }}
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
@@ -246,7 +255,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
- addDocTab={this.props.addDocTab} focus={this.props.focus} docFilters={this.props.docFilters}
+ addDocTab={this.props.addDocTab} focus={this.props.focus} docFilters={this.props.docFilters} searchFilterDocs={this.props.searchFilterDocs}
pinToPres={this.props.pinToPres} addDocument={this.addDocument}
Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index c4d8f1a4f..08160a2f4 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -488,7 +488,7 @@ $light-background: #ececec;
max-width: 200px;
overflow: visible;
-
+
.presBox-dropdownOption {
cursor: pointer;
font-size: 11;
@@ -967,10 +967,10 @@ $light-background: #ececec;
/* border: solid 1px; */
color: white;
/* box-shadow: black 0.4vw 0.4vw 0.8vw; */
-
+
.miniPresOverlay {
display: grid;
- grid-template-columns: auto auto auto auto auto auto auto auto;
+ grid-template-columns: auto auto auto auto auto auto auto auto auto auto;
grid-template-rows: 100%;
height: 100%;
justify-items: center;
@@ -1028,4 +1028,4 @@ $light-background: #ececec;
background-color: #5a5a5a;
}
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 5fc76223d..07b2d51d1 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1,34 +1,33 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, DocCastAsync, WidthSym } from "../../../fields/Doc";
-import { InkTool } from "../../../fields/InkField";
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { returnFalse, returnOne, numberRange, returnTrue } from "../../../Utils";
+import { ColorState, SketchPicker } from "react-color";
+import { Doc, DocCastAsync, DocListCast } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
-import { DocumentManager } from "../../util/DocumentManager";
-import { undoBatch } from "../../util/UndoManager";
-import { CollectionDockingView, DockedFrameRenderer } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { FieldView, FieldViewProps } from './FieldView';
-import { DocumentType } from "../../documents/DocumentTypes";
-import "./PresBox.scss";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { makeInterface, listSpec } from "../../../fields/Schema";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { InkTool } from "../../../fields/InkField";
+import { List } from "../../../fields/List";
import { PrefetchProxy } from "../../../fields/Proxy";
+import { listSpec, makeInterface } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { returnFalse, returnOne } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DocumentManager } from "../../util/DocumentManager";
import { Scripting } from "../../util/Scripting";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { List } from "../../../fields/List";
-import { Tooltip } from "@material-ui/core";
-import { actionAsync } from "mobx-utils";
import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { TabDocView } from "../collections/TabDocView";
+import { ViewBoxBaseComponent } from "../DocComponent";
import { AudioBox } from "./AudioBox";
-import { DocumentView } from "./DocumentView";
-import { SketchPicker, ColorState } from "react-color";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./PresBox.scss";
type PresBoxSchema = makeInterface<[typeof documentSchema]>;
const PresBoxDocument = makeInterface(documentSchema);
@@ -58,9 +57,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@observable private expandBoolean: boolean = false;
@observable private openMovementDropdown: boolean = false;
@observable private openEffectDropdown: boolean = false;
-
@computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
@computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); }
+ @computed get activeItem() { return Cast(this.childDocs[this.itemIndex], Doc, null); }
+ @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); }
@computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); }
constructor(props: any) {
super(props);
@@ -86,14 +86,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
} else { return undefined; }
}
@computed get isPres(): boolean {
+ document.removeEventListener("keydown", this.keyEvents, true);
if (this.selectedDoc?.type === DocumentType.PRES) {
- document.removeEventListener("keydown", this.keyEvents, true);
document.addEventListener("keydown", this.keyEvents, true);
return true;
- } else {
- document.removeEventListener("keydown", this.keyEvents, true);
- return false;
}
+ return false;
}
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
@@ -108,6 +106,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.rootDoc._replacedChrome = "replaced";
this.layoutDoc.presStatus = "edit";
this.layoutDoc._gridGap = 5;
+ if (!DocListCast((Doc.UserDoc().myPresentations as Doc).data).includes(this.rootDoc)) {
+ Doc.AddDocToList(Doc.UserDoc().myPresentations as Doc, "data", this.rootDoc);
+ }
}
updateCurrentPresentation = () => {
@@ -123,37 +124,40 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
next = () => {
this.updateCurrentPresentation();
const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null);
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const presTargetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
- const childDocs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]);
- const currentFrame = Cast(presTargetDoc.currentFrame, "number", null);
- const lastFrame = Cast(presTargetDoc.lastFrame, "number", null);
- const curFrame = NumCast(presTargetDoc.currentFrame);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
+ const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
+ const currentFrame = Cast(targetDoc._currentFrame, "number", null);
+ const lastFrame = Cast(targetDoc.lastFrame, "number", null);
+ const curFrame = NumCast(targetDoc._currentFrame);
let internalFrames: boolean = false;
- if (presTargetDoc.presProgressivize || activeItem.zoomProgressivize || presTargetDoc.scrollProgressivize) internalFrames = true;
+ if (targetDoc.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true;
// Case 1: There are still other frames and should go through all frames before going to next slide
if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) {
- presTargetDoc._viewTransition = "all 1s";
- setTimeout(() => presTargetDoc._viewTransition = undefined, 1010);
- presTargetDoc.currentFrame = curFrame + 1;
- if (presTargetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(presTargetDoc, currentFrame);
- if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, presTargetDoc);
- else presTargetDoc.editing = true;
- if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc);
+ targetDoc._viewTransition = "all 1s";
+ setTimeout(() => targetDoc._viewTransition = undefined, 1010);
+ targetDoc._currentFrame = curFrame + 1;
+ if (targetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(targetDoc, currentFrame);
+ if (targetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc);
+ else targetDoc.editing = true;
+ if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(targetDoc);
// Case 2: Audio or video therefore wait to play the audio or video before moving on
- } else if ((presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio && this.layoutDoc.presStatus !== 'auto') {
+ } else if ((targetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio && this.layoutDoc.presStatus !== 'auto') {
AudioBox.Instance.playFrom(0);
this._moveOnFromAudio = true;
// Case 3: No more frames in current doc and next slide is defined, therefore move to next slide
} else if (this.childDocs[this.itemIndex + 1] !== undefined) {
const nextSelected = this.itemIndex + 1;
- if (presTargetDoc.type === DocumentType.AUDIO) AudioBox.Instance.pause();
+ if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.pause();
this.gotoDocument(nextSelected, this.itemIndex);
const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null);
if (activeNext && targetNext.type === DocumentType.AUDIO && activeNext.playAuto) {
AudioBox.Instance.playFrom(0);
this._moveOnFromAudio = true;
} else this._moveOnFromAudio = false;
+ } else if (this.childDocs[this.itemIndex + 1] === undefined && this.layoutDoc.presLoop) {
+ const nextSelected = 0;
+ this.gotoDocument(0, this.itemIndex);
}
}
@@ -166,19 +170,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
back = () => {
this.updateCurrentPresentation();
- const docAtCurrent = this.childDocs[this.itemIndex];
- const targetDoc = Cast(docAtCurrent.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null);
const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null);
const lastFrame = Cast(targetDoc.lastFrame, "number", null);
- const curFrame = NumCast(targetDoc.currentFrame);
+ const curFrame = NumCast(targetDoc._currentFrame);
if (lastFrame !== undefined && curFrame >= 1) {
- this.prevKeyframe(targetDoc, docAtCurrent);
- } else if (docAtCurrent) {
+ this.prevKeyframe(targetDoc, activeItem);
+ } else if (activeItem) {
let prevSelected = this.itemIndex;
prevSelected = Math.max(0, prevSelected - 1);
this.gotoDocument(prevSelected, this.itemIndex);
- if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc.currentFrame = NumCast(prevTargetDoc.lastFrame);
+ if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc._currentFrame = NumCast(prevTargetDoc.lastFrame);
}
}
@@ -209,8 +213,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
* on the right.
*/
navigateToElement = async (curDoc: Doc) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
const srcContext = await DocCastAsync(targetDoc.context);
const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined;
@@ -233,7 +237,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
// If openDocument is selected then it should open the document for the user
if (activeItem.openDocument) {
- this.props.addDocTab(activeItem, "replace");
+ this.props.addDocTab(activeItem, "replace:right");
} else
//docToJump stayed same meaning, it was not in the group or was the last element in the group
if (activeItem.zoomProgressivize && this.rootDoc.presStatus !== 'edit') {
@@ -254,9 +258,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
// adjust the pan and scale to that of the pinView when it was added.
// TODO: Add option to remove presPinView
if (activeItem.presPinView) {
- targetDoc._panX = activeItem.presPinViewX;
- targetDoc._panY = activeItem.presPinViewY;
- targetDoc._viewScale = activeItem.presPinViewScale;
+ // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
+ const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
+ bestTarget && runInAction(() => {
+ bestTarget._viewTransition = "all 1s";
+ bestTarget._panX = activeItem.presPinViewX;
+ bestTarget._panY = activeItem.presPinViewY;
+ bestTarget._viewScale = activeItem.presPinViewScale;
+ });
+ //setTimeout(() => targetDoc._viewTransition = undefined, 1010);
}
// If website and has presWebsite data associated then on click it should
// go back to that specific website
@@ -271,7 +281,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
* @param presTargetDoc: document for which internal zoom is used
*/
zoomProgressivizeNext = (activeItem: Doc) => {
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const targetDoc: Doc = this.targetDoc;
const srcContext = Cast(targetDoc?.context, Doc, null);
const docView = DocumentManager.Instance.getDocumentView(targetDoc);
const vfLeft = this.checkList(targetDoc, activeItem["viewfinder-left-indexed"]);
@@ -346,8 +356,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
startAutoPres = (startSlide: number) => {
this.updateCurrentPresentation();
- let activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- let targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ let activeItem: Doc = this.activeItem;
+ let targetDoc: Doc = this.targetDoc;
let duration = NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition);
const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms));
const load = async () => { // Wrap the loop into an async function for this to work
@@ -363,17 +373,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
}
await timer(duration); this.next(); // then the created Promise can be awaited
- if (i === this.childDocs.length - 1) setTimeout(() => { clearTimeout(this._presTimer); if (this.layoutDoc.presStatus === 'auto') this.layoutDoc.presStatus = "manual"; }, duration);
+ if (i === this.childDocs.length - 1) {
+ setTimeout(() => {
+ clearTimeout(this._presTimer);
+ if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = "manual";
+ else if (this.layoutDoc.presLoop) this.startAutoPres(0);
+ }, duration);
+ }
}
};
+ this.layoutDoc.presStatus = "auto";
+ this.startPresentation(startSlide);
+ this.gotoDocument(startSlide, this.itemIndex);
+ load();
+ }
+
+ @action
+ pauseAutoPres = () => {
if (this.layoutDoc.presStatus === "auto") {
if (this._presTimer) clearTimeout(this._presTimer);
this.layoutDoc.presStatus = "manual";
- } else {
- this.layoutDoc.presStatus = "auto";
- this.startPresentation(startSlide);
- this.gotoDocument(startSlide, this.itemIndex);
- load();
+ this.layoutDoc.presLoop = false;
}
}
@@ -401,12 +421,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
startPresentation = (startIndex: number) => {
this.updateCurrentPresentation();
this.childDocs.map(doc => {
- const presTargetDoc = doc.presentationTargetDoc as Doc;
+ const tagDoc = doc.presentationTargetDoc as Doc;
if (doc.presHideTillShownButton && this.childDocs.indexOf(doc) > startIndex) {
- presTargetDoc.opacity = 0;
+ tagDoc.opacity = 0;
}
if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) {
- presTargetDoc.opacity = 0;
+ tagDoc.opacity = 0;
}
});
}
@@ -418,11 +438,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@undoBatch
@action
updateMinimize = () => {
+ const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc);
if (this.layoutDoc.inOverlay) {
this.layoutDoc.presStatus = 'edit';
- Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc);
- CollectionDockingView.AddRightSplit(this.rootDoc);
+ Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
+ CollectionDockingView.AddSplit(this.rootDoc, "right");
this.layoutDoc.inOverlay = false;
+ } else if (this.layoutDoc.context && docView) {
+ this.layoutDoc.presStatus = 'manual';
+ const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250);
+ this.rootDoc.y = pt[1] + 10;
+ this.rootDoc._height = 35;
+ this.rootDoc._width = 250;
+ docView.props.removeDocument?.(this.layoutDoc);
+ Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
} else {
this.layoutDoc.presStatus = 'manual';
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
@@ -431,7 +461,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.rootDoc._height = 35;
this.rootDoc._width = 250;
this.props.addDocTab?.(this.rootDoc, "close");
- Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc);
+ Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
}
}
@@ -453,31 +483,32 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
* When the movement dropdown is changes
*/
@undoBatch
- movementChanged = action((movement: string) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ updateMovement = action((movement: any, activeItem: Doc, targetDoc: Doc) => {
switch (movement) {
case 'zoom': //Pan and zoom
- activeItem.presZoomButton = !activeItem.presZoomButton;
- if (activeItem.presZoomButton) activeItem.presMovement = 'Zoom';
- else activeItem.presMovement = 'None';
activeItem.presNavButton = false;
+ activeItem.presZoomButton = !activeItem.presZoomButton;
+ targetDoc.presTransition = activeItem.presTransition;
+ if (activeItem.presZoomButton) activeItem.presMovement = 'zoom';
+ else activeItem.presMovement = 'none';
break;
case 'pan': //Pan
activeItem.presZoomButton = false;
activeItem.presNavButton = !activeItem.presNavButton;
- if (activeItem.presNavButton) activeItem.presMovement = 'Pan';
- else activeItem.presMovement = 'None';
+ targetDoc.presTransition = activeItem.presTransition;
+ if (activeItem.presNavButton) activeItem.presMovement = 'pan';
+ else activeItem.presMovement = 'none';
break;
case 'jump': //Jump Cut
+ activeItem.presTransition = targetDoc.presTransition;
targetDoc.presTransition = 0;
activeItem.presZoomButton = true;
activeItem.presSwitchButton = !activeItem.presSwitchButton;
- if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut';
- else activeItem.presMovement = 'None';
+ if (activeItem.presSwitchButton) activeItem.presMovement = 'jump';
+ else activeItem.presMovement = 'none';
break;
case 'none': default:
- activeItem.presMovement = 'None';
+ activeItem.presMovement = 'none';
activeItem.presZoomButton = false;
activeItem.presNavButton = false;
activeItem.presSwitchButton = false;
@@ -485,6 +516,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
});
+ setMovementName = action((movement: any, activeItem: Doc): string => {
+ let output: string = 'none';
+ switch (movement) {
+ case 'zoom': output = 'Zoom'; break; //Pan and zoom
+ case 'pan': output = 'Pan'; break; //Pan
+ case 'jump': output = 'Jump cut'; break; //Jump Cut
+ case 'none': output = 'None'; break; //None
+ default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom
+ }
+ return output;
+ });
+
whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
// For dragging documents into the presentation trail
addDocumentFilter = (doc: Doc | Doc[]) => {
@@ -501,10 +544,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
return true;
}
childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
+ removeDocument = (doc: Doc) => { Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); };
getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
panelHeight = () => this.props.PanelHeight() - 40;
- active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground) &&
+ active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground) &&
(this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
/**
@@ -526,10 +569,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
*/
@computed get listOfSelected() {
const list = this._selectedArray.map((doc: Doc, index: any) => {
- const activeItem = Cast(doc, Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc!, Doc, null);
+ const curDoc = Cast(doc, Doc, null);
+ const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null);
return (
- <div className="selectedList-items">{index + 1}. {targetDoc.title}</div>
+ <div className="selectedList-items">{index + 1}. {tagDoc.title}</div>
);
});
return list;
@@ -563,8 +606,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
this._selectedArray = [];
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- if (activeItem) {
+ // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ if (this.activeItem) {
for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) {
this._selectedArray.push(this.childDocs[i]);
this._eleArray.push(ref);
@@ -575,8 +618,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
// Key for when the presentaiton is active
- @action
- keyEvents = (e: KeyboardEvent) => {
+ @undoBatch
+ keyEvents = action((e: KeyboardEvent) => {
+ if (e.target instanceof HTMLInputElement) return;
let handled = false;
const anchorNode = document.activeElement as HTMLDivElement;
if (anchorNode && anchorNode.className?.includes("lm_title")) return;
@@ -592,29 +636,44 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
handled = true;
}
} if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back
- this.back(); if (this._presTimer) clearTimeout(this._presTimer);
+ this.back();
+ if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
} if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next
- this.next(); if (this._presTimer) clearTimeout(this._presTimer);
+ this.next();
+ if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
} if (e.keyCode === 32) { // spacebar to 'present' or autoplay
if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0);
else this.layoutDoc.presStatus = "manual"; if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
- }
- if (e.keyCode === 8) { // delete selected items
+ } if (e.keyCode === 8) { // delete selected items
if (this.layoutDoc.presStatus === "edit") {
- this._selectedArray.forEach((doc, i) => {
- this.removeDocument(doc);
- });
+ this._selectedArray.forEach((doc, i) => this.removeDocument(doc));
+ this._selectedArray = [];
+ this._eleArray = [];
+ this._dragArray = [];
handled = true;
}
- }
- if (handled) {
+ } if (handled) {
e.stopPropagation();
e.preventDefault();
+ } if ((e.keyCode === 37 || e.keyCode === 38) && e.shiftKey) { // left(37) / a(65) / up(38) to go back
+ if (this.layoutDoc.presStatus === "edit" && this._selectedArray.length > 0) {
+ const index = this.childDocs.indexOf(this._selectedArray[this._selectedArray.length]);
+ if ((index - 1) > 0) this._selectedArray.push(this.childDocs[index - 1]);
+ }
+ handled = true;
+ } if ((e.keyCode === 39 || e.keyCode === 40) && e.shiftKey) { // left(37) / a(65) / up(38) to go back
+ if (this.layoutDoc.presStatus === "edit" && this._selectedArray.length > 0) {
+ const index = this.childDocs.indexOf(this._selectedArray[this._selectedArray.length]);
+ if ((index - 1) > 0) {
+ this._selectedArray.push(this.childDocs[index - 1]);
+ }
+ }
+ handled = true;
}
- }
+ });
/**
*
@@ -636,12 +695,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get order() {
const order: JSX.Element[] = [];
this.childDocs.forEach((doc, index) => {
- const targetDoc = Cast(doc.presentationTargetDoc, Doc, null);
- const srcContext = Cast(targetDoc?.context, Doc, null);
+ const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const srcContext = Cast(tagDoc?.context, Doc, null);
// Case A: Document is contained within the colleciton
if (this.rootDoc.presCollection === srcContext) {
order.push(
- <div className="pathOrder" style={{ top: NumCast(targetDoc.y), left: NumCast(targetDoc.x) }}>
+ <div className="pathOrder" style={{ top: NumCast(tagDoc.y), left: NumCast(tagDoc.x) }}>
<div className="pathOrder-frame">{index + 1}</div>
</div>);
// Case B: Document is not inside of the collection
@@ -666,11 +725,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get paths() {
let pathPoints = "";
this.childDocs.forEach((doc, index) => {
- const targetDoc = Cast(doc.presentationTargetDoc, Doc, null);
- const srcContext = Cast(targetDoc?.context, Doc, null);
- if (targetDoc && this.rootDoc.presCollection === srcContext) {
- const n1x = NumCast(targetDoc.x) + (NumCast(targetDoc._width) / 2);
- const n1y = NumCast(targetDoc.y) + (NumCast(targetDoc._height) / 2);
+ const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const srcContext = Cast(tagDoc?.context, Doc, null);
+ if (tagDoc && this.rootDoc.presCollection === srcContext) {
+ const n1x = NumCast(tagDoc.x) + (NumCast(tagDoc._width) / 2);
+ const n1y = NumCast(tagDoc.y) + (NumCast(tagDoc._height) / 2);
if (index = 0) pathPoints = n1x + "," + n1y;
else pathPoints = pathPoints + " " + n1x + "," + n1y;
} else {
@@ -701,8 +760,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
activeItem.presFadeButton = !activeItem.presFadeButton;
if (!activeItem.presFadeButton) {
if (targetDoc) {
@@ -722,9 +781,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
if (timeInMS > 10000) timeInMS = 10000;
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
- if (targetDoc) targetDoc.presTransition = timeInMS;
+ if (this.targetDoc) this.targetDoc.presTransition = timeInMS;
}
// Converts seconds to ms and updates presDuration
@@ -733,15 +790,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
if (timeInMS > 20000) timeInMS = 20000;
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
- if (targetDoc) targetDoc.presDuration = timeInMS;
+ if (this.targetDoc) this.targetDoc.presDuration = timeInMS;
}
@computed get transitionDropdown() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
if (activeItem && targetDoc) {
const transitionSpeed = targetDoc.presTransition ? NumCast(targetDoc.presTransition) / 1000 : 0.5;
let duration = targetDoc.presDuration ? NumCast(targetDoc.presDuration) / 1000 : 2;
@@ -753,21 +808,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-box">
Movement
<div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? 'solid 2px #5B9FDD' : 'solid 1px black' }}>
- {activeItem.presMovement}
+ {this.setMovementName(activeItem.presMovement, activeItem)}
<FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === 'None' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('none')}>None</div>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('zoom')}>Pan and Zoom</div>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('pan')}>Pan</div>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Jump cut' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('jump')}>Jump cut</div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === 'none' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('none', activeItem, targetDoc)}>None</div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === 'zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('zoom', activeItem, targetDoc)}>Pan and Zoom</div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === 'pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('pan', activeItem, targetDoc)}>Pan</div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === 'jump' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('jump', activeItem, targetDoc)}>Jump cut</div>
</div>
</div>
- <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "inline-flex" : "none" }}>
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "inline-flex" : "none" }}>
<div className="presBox-subheading">Transition Speed</div>
<div className="ribbon-property">
<input className="presBox-input"
type="number" value={transitionSpeed}
- onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+ onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
onChange={action((e) => this.setTransitionTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
@@ -779,8 +834,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
</div>
</div>
- <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} />
- <div className={`slider-headers ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`}>
+ <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} />
+ <div className={`slider-headers ${activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "" : "none"}`}>
<div className="slider-text">Fast</div>
<div className="slider-text">Medium</div>
<div className="slider-text">Slow</div>
@@ -797,7 +852,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-property">
<input className="presBox-input"
type="number" value={duration}
- onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+ onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
onChange={action((e) => this.setDurationTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
@@ -858,10 +913,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
@computed get effectDirection(): string {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
let effect = '';
- switch (targetDoc.presEffectDirection) {
+ switch (this.targetDoc.presEffectDirection) {
case 'left': effect = "Enter from left"; break;
case 'right': effect = "Enter from right"; break;
case 'top': effect = "Enter from top"; break;
@@ -874,8 +927,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@undoBatch
@action
applyTo = (array: Doc[]) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
array.forEach((doc, index) => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
@@ -885,14 +938,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
tagDoc.presEffect = targetDoc.presEffect;
tagDoc.presEffectDirection = targetDoc.presEffectDirection;
curDoc.presMovement = activeItem.presMovement;
+ this.updateMovement(activeItem.presMovement, curDoc, tagDoc);
curDoc.presHideTillShownButton = activeItem.presHideTillShownButton;
curDoc.presHideAfterButton = activeItem.presHideAfterButton;
}
});
}
@computed get optionsDropdown() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
if (activeItem && targetDoc) {
return (
<div>
@@ -909,6 +963,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-button" style={{ backgroundColor: activeItem.presPinView ? "#aedef8" : "" }}
onClick={() => {
activeItem.presPinView = !activeItem.presPinView;
+ targetDoc.presPinView = activeItem.presPinView;
if (activeItem.presPinView) {
const x = targetDoc._panX;
const y = targetDoc._panY;
@@ -926,7 +981,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewX)}
- onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+ onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} />
</div>
</div>
@@ -936,7 +991,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewY)}
- onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+ onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} />
</div>
</div>
@@ -946,7 +1001,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewScale)}
- onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }}
+ onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} />
</div>
</div>
@@ -996,9 +1051,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className="ribbon-box">
Slide Title: <br></br>
<input className="ribbon-textInput" placeholder="..." type="text" name="fname"
- onFocus={() => {
- document.removeEventListener("keydown", this.keyEvents, true);
- }}
+ onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)}
onChange={(e) => {
e.stopPropagation();
e.preventDefault();
@@ -1052,21 +1105,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (layout) doc = this.createTemplate(layout);
if (freeform && layout) doc = this.createTemplate(layout, title);
if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title });
- const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
- const data = Cast(presCollection?.data, listSpec(Doc));
- const presData = Cast(this.rootDoc.data, listSpec(Doc));
- if (data && doc && presData) {
- data.push(doc);
- DockedFrameRenderer.PinDoc(doc, false);
- this.gotoDocument(this.childDocs.length, this.itemIndex);
- } else {
- this.props.addDocTab(doc as Doc, "onRight");
+ if (doc) {
+ const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
+ const data = Cast(presCollection?.data, listSpec(Doc));
+ const presData = Cast(this.rootDoc.data, listSpec(Doc));
+ if (data && presData) {
+ data.push(doc);
+ TabDocView.PinDoc(doc, false);
+ this.gotoDocument(this.childDocs.length, this.itemIndex);
+ } else {
+ this.props.addDocTab(doc, "add:right");
+ }
}
}
createTemplate = (layout: string, input?: string) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
let x = 0;
let y = 0;
if (activeItem && targetDoc) {
@@ -1120,9 +1175,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
// Case in which the document has keyframes to navigate to next key frame
@undoBatch
@action
- nextKeyframe = (tagDoc: Doc, activeItem: Doc): void => {
+ nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => {
const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
- const currentFrame = Cast(tagDoc.currentFrame, "number", null);
+ const currentFrame = Cast(tagDoc._currentFrame, "number", null);
if (currentFrame === undefined) {
tagDoc.currentFrame = 0;
CollectionFreeFormDocumentView.setupScroll(tagDoc, 0);
@@ -1130,37 +1185,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame);
CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc);
- tagDoc.currentFrame = Math.max(0, (currentFrame || 0) + 1);
- tagDoc.lastFrame = Math.max(NumCast(tagDoc.currentFrame), NumCast(tagDoc.lastFrame));
- if (activeItem.zoomProgressivize) {
+ tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame));
+ if (curDoc.zoomProgressivize) {
const resize = document.getElementById('resizable');
if (resize) {
- resize.style.width = this.checkList(tagDoc, activeItem["viewfinder-width-indexed"]) + 'px';
- resize.style.height = this.checkList(tagDoc, activeItem["viewfinder-height-indexed"]) + 'px';
- resize.style.top = this.checkList(tagDoc, activeItem["viewfinder-top-indexed"]) + 'px';
- resize.style.left = this.checkList(tagDoc, activeItem["viewfinder-left-indexed"]) + 'px';
+ resize.style.width = this.checkList(tagDoc, curDoc["viewfinder-width-indexed"]) + 'px';
+ resize.style.height = this.checkList(tagDoc, curDoc["viewfinder-height-indexed"]) + 'px';
+ resize.style.top = this.checkList(tagDoc, curDoc["viewfinder-top-indexed"]) + 'px';
+ resize.style.left = this.checkList(tagDoc, curDoc["viewfinder-left-indexed"]) + 'px';
}
}
}
@undoBatch
@action
- prevKeyframe = (tagDoc: Doc, activeItem: Doc): void => {
+ prevKeyframe = (tagDoc: Doc, actItem: Doc): void => {
const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
- const currentFrame = Cast(tagDoc.currentFrame, "number", null);
+ const currentFrame = Cast(tagDoc._currentFrame, "number", null);
if (currentFrame === undefined) {
- tagDoc.currentFrame = 0;
+ tagDoc._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice());
- tagDoc.currentFrame = Math.max(0, (currentFrame || 0) - 1);
- if (activeItem.zoomProgressivize) {
+ tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ if (actItem.zoomProgressivize) {
const resize = document.getElementById('resizable');
if (resize) {
- resize.style.width = this.checkList(tagDoc, activeItem["viewfinder-width-indexed"]) + 'px';
- resize.style.height = this.checkList(tagDoc, activeItem["viewfinder-height-indexed"]) + 'px';
- resize.style.top = this.checkList(tagDoc, activeItem["viewfinder-top-indexed"]) + 'px';
- resize.style.left = this.checkList(tagDoc, activeItem["viewfinder-left-indexed"]) + 'px';
+ resize.style.width = this.checkList(tagDoc, actItem["viewfinder-width-indexed"]) + 'px';
+ resize.style.height = this.checkList(tagDoc, actItem["viewfinder-height-indexed"]) + 'px';
+ resize.style.top = this.checkList(tagDoc, actItem["viewfinder-top-indexed"]) + 'px';
+ resize.style.left = this.checkList(tagDoc, actItem["viewfinder-left-indexed"]) + 'px';
}
}
}
@@ -1169,8 +1224,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
* Returns the collection type as a string for headers
*/
@computed get stringType(): string {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
let type: string = '';
if (activeItem) {
switch (targetDoc.type) {
@@ -1193,11 +1248,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get progressivizeDropdown() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
- const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black";
- const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black";
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
if (activeItem && targetDoc) {
+ const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black";
+ const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black";
return (
<div>
<div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
@@ -1209,7 +1264,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
<div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}>
<div className="presBox-subheading">Active text color</div>
- <div className="ribbon-property" style={{ backgroundColor: activeFontColor }} onClick={action(() => { console.log("hi"); this.openActiveColorPicker = !this.openActiveColorPicker; })}>
+ <div className="ribbon-property" style={{ backgroundColor: activeFontColor }} onClick={action(() => { this.openActiveColorPicker = !this.openActiveColorPicker; })}>
</div>
</div>
{this.activeColorPicker}
@@ -1237,7 +1292,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
<div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.editing ? "white" : "black", backgroundColor: targetDoc.editing ? "#5B9FDD" : "#AEDDF8" }}
onClick={action(() => targetDoc.editing = !targetDoc.editing)} >
- {NumCast(targetDoc.currentFrame)}
+ {NumCast(targetDoc._currentFrame)}
</div>
<div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}>
<FontAwesomeIcon icon={"caret-right"} size={"lg"} />
@@ -1256,8 +1311,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@undoBatch
@action
switchActive = (color: ColorState) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
const val = String(color.hex);
targetDoc["pres-text-color"] = val;
return true;
@@ -1265,16 +1320,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@undoBatch
@action
switchPresented = (color: ColorState) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
const val = String(color.hex);
targetDoc["pres-text-viewed-color"] = val;
return true;
}
@computed get activeColorPicker() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
return !this.openActiveColorPicker ? (null) : <SketchPicker onChange={this.switchActive}
presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
'#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
@@ -1283,8 +1338,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
@computed get viewedColorPicker() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
return !this.openViewedColorPicker ? (null) : <SketchPicker onChange={this.switchPresented}
presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
'#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
@@ -1308,8 +1363,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
//Toggle whether the user edits or not
@action
editZoomProgressivize = (e: React.MouseEvent) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
if (!targetDoc.editZoomProgressivize) {
if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true;
targetDoc.editZoomProgressivize = true;
@@ -1323,8 +1378,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
//Toggle whether the user edits or not
@action
editScrollProgressivize = (e: React.MouseEvent) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
if (!targetDoc.editScrollProgressivize) {
if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; }
targetDoc.editScrollProgressivize = true;
@@ -1337,14 +1392,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
progressivizeScroll = (e: React.MouseEvent) => {
e.stopPropagation();
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const activeItem: Doc = this.activeItem;
activeItem.scrollProgressivize = !activeItem.scrollProgressivize;
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const targetDoc: Doc = this.targetDoc;
targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize;
- CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc.currentFrame), true);
+ CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame));
if (targetDoc.editScrollProgressivize) {
targetDoc.editScrollProgressivize = false;
- targetDoc.currentFrame = 0;
+ targetDoc._currentFrame = 0;
targetDoc.lastFrame = 0;
}
}
@@ -1353,14 +1408,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
progressivizeZoom = (e: React.MouseEvent) => {
e.stopPropagation();
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const activeItem: Doc = this.activeItem;
activeItem.zoomProgressivize = !activeItem.zoomProgressivize;
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const targetDoc: Doc = this.targetDoc;
targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize;
- CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc, true);
+ CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc);
if (activeItem.editZoomProgressivize) {
activeItem.editZoomProgressivize = false;
- targetDoc.currentFrame = 0;
+ targetDoc._currentFrame = 0;
targetDoc.lastFrame = 0;
}
}
@@ -1368,9 +1423,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
//Progressivize Child Docs
@action
editProgressivize = (e: React.MouseEvent) => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
- targetDoc.currentFrame = targetDoc.lastFrame;
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
+ targetDoc._currentFrame = targetDoc.lastFrame;
if (!targetDoc.editProgressivize) {
if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; }
targetDoc.editProgressivize = true;
@@ -1382,21 +1437,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
progressivizeChild = (e: React.MouseEvent) => {
e.stopPropagation();
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const activeItem: Doc = this.activeItem;
+ const targetDoc: Doc = this.targetDoc;
const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
if (!activeItem.presProgressivize) {
targetDoc.editing = false;
activeItem.presProgressivize = true;
targetDoc.presProgressivize = true;
- targetDoc.currentFrame = 0;
- CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
+ targetDoc._currentFrame = 0;
+ docs.forEach((doc, i) => CollectionFreeFormDocumentView.setupKeyframes([doc], i, true));
targetDoc.lastFrame = docs.length - 1;
} else {
targetDoc.editProgressivize = false;
activeItem.presProgressivize = false;
targetDoc.presProgressivize = false;
- targetDoc.currentFrame = 0;
+ targetDoc._currentFrame = 0;
targetDoc.lastFrame = 0;
targetDoc.editing = true;
}
@@ -1438,18 +1493,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
checkList = (doc: Doc, list: any): number => {
const x: List<number> = list;
- if (x && x.length >= NumCast(doc.currentFrame) + 1) {
- return x[NumCast(doc.currentFrame)];
+ if (x && x.length >= NumCast(doc._currentFrame) + 1) {
+ return x[NumCast(doc._currentFrame)];
} else if (x) {
- x.length = NumCast(doc.currentFrame) + 1;
- x[NumCast(doc.currentFrame)] = x[NumCast(doc.currentFrame) - 1];
- return x[NumCast(doc.currentFrame)];
+ x.length = NumCast(doc._currentFrame) + 1;
+ x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1];
+ return x[NumCast(doc._currentFrame)];
} else return 100;
}
@computed get progressivizeChildDocs() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const targetDoc: Doc = this.targetDoc;
const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
const tags: JSX.Element[] = [];
docs.forEach((doc, index) => {
@@ -1457,7 +1511,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>);
}
tags.push(
- <div className="progressivizeButton" onPointerLeave={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
+ <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
<div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div>
<div className="progressivizeButton-frame">{doc.appearFrame}</div>
<div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div>
@@ -1469,8 +1523,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@undoBatch
@action
nextAppearFrame = (doc: Doc, i: number): void => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
const appearFrame = Cast(doc.appearFrame, "number", null);
if (appearFrame === undefined) {
doc.appearFrame = 0;
@@ -1482,8 +1536,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@undoBatch
@action
prevAppearFrame = (doc: Doc, i: number): void => {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
const appearFrame = Cast(doc.appearFrame, "number", null);
if (appearFrame === undefined) {
doc.appearFrame = 0;
@@ -1604,12 +1658,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
@computed get playButtonFrames() {
- const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
- const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const targetDoc: Doc = this.targetDoc;
return (
<>
- {targetDoc ? <div className="miniPres-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}>
- <div>{targetDoc.currentFrame}</div>
+ {this.targetDoc ? <div className="miniPres-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}>
+ <div>{targetDoc._currentFrame}</div>
<div className="miniPres-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
<div>{targetDoc.lastFrame}</div>
</div> : null}
@@ -1620,8 +1673,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get playButtons() {
// Case 1: There are still other frames and should go through all frames before going to next slide
return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <div className="presPanel-divider"></div>
<div className="presPanel-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div>
- <div className="presPanel-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
<div className="presPanel-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div>
<div className="presPanel-divider"></div>
<div className="presPanel-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}>
@@ -1636,6 +1691,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>);
}
+ @action
+ startOrPause = () => {
+ if (this.layoutDoc.presStatus === "manual") this.startAutoPres(this.itemIndex);
+ else this.pauseAutoPres();
+ }
+
render() {
// calling this method for keyEvents
this.isPres;
@@ -1645,8 +1706,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
return this.layoutDoc.inOverlay ?
<div className="miniPres" style={{ width: 250, height: 35, background: '#323232', top: 0, zIndex: 3000000 }}>
{<div className="miniPresOverlay">
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="miniPres-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <div className="miniPres-divider"></div>
<div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div>
- <div className="miniPres-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="miniPres-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
<div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div>
<div className="miniPres-divider"></div>
<div className="miniPres-button-text">
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index bd5b3bff4..8876b4879 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,13 +1,9 @@
import React = require("react");
-import { observable, action } from "mobx";
-import { observer } from "mobx-react";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { observer } from "mobx-react";
import { UndoManager } from "../../util/UndoManager";
-library.add(faAngleRight);
-
export interface RadialMenuProps {
description: string;
event: (stuff?: any) => void;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 1cd29d795..5d51c420b 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,12 +1,11 @@
import React = require("react");
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faVideo } from "@fortawesome/free-solid-svg-icons";
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 { Doc } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
+import { InkTool } from "../../../fields/InkField";
import { listSpec, makeInterface } from "../../../fields/Schema";
import { Cast, NumCast } from "../../../fields/Types";
import { VideoField } from "../../../fields/URLField";
@@ -18,14 +17,11 @@ import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxBaseComponent } from "../DocComponent";
import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
-import { InkTool } from "../../../fields/InkField";
const path = require('path');
type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
const ScreenshotDocument = makeInterface(documentSchema);
-library.add(faVideo);
-
@observer
export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
private _reactionDisposer?: IReactionDisposer;
@@ -77,7 +73,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
const spt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
imageSummary.x = spt[0];
imageSummary.y = spt[1];
- Cast(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null)?.data, listSpec(Doc), []).push(imageSummary);
+ Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null)?.data, listSpec(Doc), []).push(imageSummary);
} else {
this.props.addDocument?.(imageSummary);
}
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 45cdfc5ad..d13680046 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-regular-svg-icons';
import { runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -16,9 +14,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import { Handle, Tick, TooltipRail, Track } from './SliderBox-components';
import './SliderBox.scss';
-
-library.add(faEdit as any);
-
const SliderSchema = createSchema({
_sliderMin: "number",
_sliderMax: "number",
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ee92e517c..d50a10bdd 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -23,7 +23,7 @@ import { SnappingManager } from "../../util/SnappingManager";
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
+ _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 timeSchema]>;
const VideoDocument = makeInterface(documentSchema, timeSchema);
@@ -82,7 +82,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
@action public FullScreen() {
this._fullScreen = true;
this.player && this.player.requestFullscreen();
- this._youtubePlayer && this.props.addDocTab(this.rootDoc, "inTab");
+ this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add");
}
choosePath(url: string) {
@@ -109,14 +109,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
if (!this._videoRef) {
const b = Docs.Create.LabelDocument({
x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1),
- _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString(),
+ _width: 150, _height: 50, title: (this.layoutDoc._currentTimecode || 0).toString(),
});
b.isLinkButton = true;
this.props.addDocument?.(b);
DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, "video snapshot");
Networking.PostToServer("/youtubeScreenshot", {
id: this.youtubeVideoId,
- timecode: this.layoutDoc.currentTimecode
+ timecode: this.layoutDoc._currentTimecode
}).then(response => {
const resolved = response?.accessPaths?.agnostic?.client;
if (resolved) {
@@ -128,7 +128,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc.currentTimecode || 0).toString().replace(/\./, "_")));
+ const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_")));
VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => {
if (returnedFilename) {
this.createRealSummaryLink(returnedFilename);
@@ -144,7 +144,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
const imageSummary = Docs.Create.ImageDocument(url, {
_nativeWidth: this.layoutDoc._nativeWidth, _nativeHeight: this.layoutDoc._nativeHeight,
x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0),
- _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc.currentTimecode || 0) + " image-"
+ _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc._currentTimecode || 0) + " image-"
});
Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth;
Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight;
@@ -155,8 +155,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
@action
updateTimecode = () => {
- this.player && (this.layoutDoc.currentTimecode = this.player.currentTime);
- this._youtubePlayer && (this.layoutDoc.currentTimecode = this._youtubePlayer.getCurrentTime());
+ this.player && (this.layoutDoc._currentTimecode = this.player.currentTime);
+ this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime());
}
componentDidMount() {
@@ -185,9 +185,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._videoRef = vref;
if (vref) {
this._videoRef!.ontimeupdate = this.updateTimecode;
+ // @ts-ignore
vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
- this._reactionDisposer && this._reactionDisposer();
- this._reactionDisposer = reaction(() => (this.layoutDoc.currentTimecode || 0),
+ this._reactionDisposer?.();
+ this._reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0),
time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
}
}
@@ -272,21 +273,21 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
const onYoutubePlayerReady = (event: any) => {
this._reactionDisposer?.();
this._youtubeReactionDisposer?.();
- this._reactionDisposer = reaction(() => this.layoutDoc.currentTimecode, () => !this._playing && this.Seek((this.layoutDoc.currentTimecode || 0)));
+ this._reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0)));
this._youtubeReactionDisposer = reaction(
- () => Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
+ () => !this.props.Document.isAnnotating && Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
(interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true });
};
this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
events: {
- 'onReady': onYoutubePlayerReady,
- 'onStateChange': onYoutubePlayerStateChange,
+ 'onReady': this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
+ 'onStateChange': this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
}
});
}
private get uIButtons() {
- const curTime = (this.layoutDoc.currentTimecode || 0);
+ const curTime = (this.layoutDoc._currentTimecode || 0);
return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} >
<span>{"" + Math.round(curTime)}</span>
<span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
@@ -328,7 +329,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
onResetMove = (e: PointerEvent) => {
this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
- this.Seek(Math.max(0, (this.layoutDoc.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
+ this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
e.stopImmediatePropagation();
}
@@ -336,14 +337,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
onResetUp = (e: PointerEvent) => {
document.removeEventListener("pointermove", this.onResetMove, true);
document.removeEventListener("pointerup", this.onResetUp, true);
- this._isResetClick < 10 && (this.layoutDoc.currentTimecode = 0);
+ this._isResetClick < 10 && (this.layoutDoc._currentTimecode = 0);
}
@computed get youtubeContent() {
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
- const start = untracked(() => Math.round((this.layoutDoc.currentTimecode || 0)));
+ const start = untracked(() => Math.round((this.layoutDoc._currentTimecode || 0)));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.layoutDoc._nativeWidth || 640)} height={(this.layoutDoc._nativeHeight || 390)}
src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
@@ -353,7 +354,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
addDocumentWithTimestamp(doc: Doc | Doc[]): boolean {
const docs = doc instanceof Doc ? [doc] : doc;
docs.forEach(doc => {
- const curTime = (this.layoutDoc.currentTimecode || -1);
+ const curTime = (this.layoutDoc._currentTimecode || -1);
curTime !== -1 && (doc.displayTimecode = curTime);
});
return this.addDocument(doc);
@@ -384,6 +385,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
renderDepth={this.props.renderDepth + 1}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
{this.contentFunc}
</CollectionFreeFormView>
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 875142169..134860d0a 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -70,7 +70,6 @@
}
}
-
.webBox-overlay {
width: 100%;
height: 100%;
@@ -82,61 +81,24 @@
background:lightGray;
width: 100%;
}
- .webBox-freeze {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 5px;
- width: 30px;
- }
-
- .webBox-urlEditor {
- position: relative;
- opacity: 0.9;
+ .webBox-annotationToggle {
z-index: 9001;
- transition: top .5s;
-
- .urlEditor {
- display: grid;
- grid-template-columns: 1fr auto;
- padding-bottom: 10px;
- overflow: hidden;
-
- .editorBase {
- display: flex;
-
- .editor-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 40px;
- transform-origin: top left;
- }
-
- .switchToText {
- color: $main-accent;
- }
-
- .switchToText:hover {
- color: $dark-color;
- }
- }
-
- button:hover {
- transform: scale(1);
- }
+ position: absolute;
+ top: 2;
+ left: 2;
+ box-shadow: black 0.3em 0.3em 1em;
+ border-radius: 5px;
+ display: flex;
+ opacity: 0.3;
+ width: 25px;
+ height: 25px;
+ align-items: center;
+ > svg {
+ margin: auto;
}
}
-
- .webpage-urlInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: grey;
- letter-spacing: 2px;
- outline-color: black;
- background: rgb(238, 238, 238);
- width: 100%;
- margin-right: 10px;
- height: 100%;
+ .webBox-annotationToggle:hover {
+ opacity: 1;
}
.touch-iframe-overlay {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1393e7868..c5d7c3c9f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -24,13 +24,14 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
-import Annotation from "../pdf/Annotation";
-import PDFMenu from "../pdf/PDFMenu";
+import { Annotation } from "../pdf/Annotation";
+import { PDFMenu } from "../pdf/PDFMenu";
import { PdfViewerMarquee } from "../pdf/PDFViewer";
import { FieldView, FieldViewProps } from './FieldView';
import "./WebBox.scss";
import "../pdf/PDFViewer.scss";
import React = require("react");
+import { Tooltip } from '@material-ui/core';
const htmlToText = require("html-to-text");
type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -235,81 +236,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
e.stopPropagation();
}
- toggleAnnotationMode = () => {
- this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating;
- }
-
- urlEditor() {
- return (
- <div className="webBox-urlEditor"
- onDrop={this.onUrlDrop}
- onDragOver={this.onUrlDragover} style={{ top: this._collapsed ? -70 : 0 }}>
- <div className="urlEditor">
- <div className="editorBase">
- <button className="editor-collapse"
- style={{
- 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"),
- }}
- title="Collapse Url Editor" onClick={this.toggleCollapse}>
- <FontAwesomeIcon icon="caret-up" size="2x" />
- </button>
- <div className="webBox-buttons"
- onDrop={this.onUrlDrop}
- onDragOver={this.onUrlDragover} style={{ display: this._collapsed ? "none" : "flex" }}>
- <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: !this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
- <FontAwesomeIcon icon={faMousePointer} size={"2x"} />
- </div>
- <input className="webpage-urlInput"
- placeholder="ENTER URL"
- value={this._url}
- onDrop={this.onUrlDrop}
- onDragOver={this.onUrlDragover}
- onChange={this.onURLChange}
- onKeyDown={this.onValueKeyDown}
- onClick={(e) => {
- this._keyInput.current!.select();
- e.stopPropagation();
- }}
- ref={this._keyInput}
- />
- <div style={{
- display: "flex",
- flexDirection: "row",
- justifyContent: "space-between",
- maxWidth: "120px",
- }}>
- <button className="submitUrl" onClick={this.submitURL}
- onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
- GO
- </button>
- <button className="submitUrl" onClick={this.back}>
- <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
- </button>
- <button className="submitUrl" onClick={this.forward}>
- <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
- </button>
- </div>
- </div>
- </div>
- </div>
+ editToggleBtn() {
+ return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}>
+ <div className="webBox-annotationToggle"
+ style={{ color: this.props.Document.isAnnotating ? "black" : "white", backgroundColor: this.props.Document.isAnnotating ? "white" : "black" }}
+ onClick={action(() => this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating)}>
+ <FontAwesomeIcon icon="edit" size="sm" />
</div>
- );
+ </Tooltip>;
}
-
- @action
- toggleCollapse = () => {
- this._collapsed = !this._collapsed;
- }
-
-
-
_ignore = 0;
onPreWheel = (e: React.WheelEvent) => {
this._ignore = e.timeStamp;
@@ -438,7 +374,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (this.layoutDoc.UseCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.UseCors = !this.layoutDoc.UseCors, icon: "snowflake" });
+ funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
@@ -453,7 +389,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (field instanceof HtmlField) {
view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href;
+ const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded}
// the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
@@ -474,16 +410,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
style={{ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%" }}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{view}
- </div>;
+ </div>
{!frozen ? (null) :
- <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }}
+ <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc._isBackground ? undefined : "all" }}
onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
<div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
<div className="indicator" ref={this._iframeIndicatorRef}></div>
<div className="dragger" ref={this._iframeDragRef}></div>
</div>
</div>}
- {this.urlEditor()}
</>);
}
@@ -560,12 +495,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
clipDoc.rootDocument = targetDoc;
targetDoc.layoutKey = "layout";
- const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
+ const annotationDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
if (annotationDoc) {
DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
dragComplete: e => {
- if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
- DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+ if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) {
+ e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
annotationDoc.isLinkButton = true;
}
}
@@ -682,7 +617,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
transform: `scale(${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
+ pointerEvents: this.layoutDoc._isBackground ? "none" : undefined
}}
onContextMenu={this.specificContextMenu}>
<base target="_blank" />
@@ -690,7 +625,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%",
- pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none"
+ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc._isBackground ? "all" : "none"
}}
onWheel={e => e.stopPropagation()}
onPointerDown={this.onMarqueeDown}
@@ -709,7 +644,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}}>
<div className={"webBox-innerContent"} style={{
height: NumCast(this.layoutDoc.scrollHeight),
- pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+ pointerEvents: this.layoutDoc._isBackground ? "none" : undefined
}}>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
@@ -733,6 +668,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
ScreenToLocalTransform={this.scrollXf}
renderDepth={this.props.renderDepth + 1}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
</CollectionFreeFormView>
</div>
@@ -740,6 +676,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
{this.annotationLayer}
<PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
</div >
+
+ {this.props.isSelected() ? this.editToggleBtn() : null}
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 212da3f3d..3b77735a7 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -5,13 +5,14 @@ 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, returnEmptyFilter } from "../../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero, returnEmptyFilter, returnEmptyDoclist } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentView } from "../DocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
import { Transform } from "../../../util/Transform";
import React = require("react");
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
interface IDashDocView {
node: any;
@@ -114,7 +115,7 @@ export class DashDocView extends React.Component<IDashDocView> {
}
/*endregion*/
- componentWillMount = () => {
+ componentWillUnmount = () => {
this._reactionDisposer?.();
}
@@ -175,7 +176,7 @@ export class DashDocView extends React.Component<IDashDocView> {
const outerStyle = {
position: "relative" as "relative",
textIndent: "0",
- border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")),
+ border: "1px solid " + StrCast(this._textBox.Document.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")),
width: this.props.node.props.width,
height: this.props.node.props.height,
display: this.props.node.props.hidden ? "none" : "inline-block",
@@ -202,7 +203,7 @@ export class DashDocView extends React.Component<IDashDocView> {
({ dim, color }) => {
spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px";
spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px";
- outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+ outerStyle.border = "1px solid " + StrCast(finalLayout.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
}, { fireImmediately: true });
if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
@@ -254,7 +255,8 @@ export class DashDocView extends React.Component<IDashDocView> {
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
dontRegisterView={false}
- docFilters={this.props.tbox?.props.docFilters||returnEmptyFilter}
+ docFilters={this.props.tbox?.props.docFilters || returnEmptyFilter}
+ searchFilterDocs={this.props.tbox?.props.searchFilterDocs || returnEmptyDoclist}
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
ContentScaling={this.contentScaling}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8ae71c035..f2658e77e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -169,7 +169,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
onPointerDownEnumerables = async (e: any) => {
e.stopPropagation();
const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
- collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
+ collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right");
}
@@ -183,7 +183,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
if (container) {
const alias = Doc.MakeAlias(container.props.Document);
- alias.viewType = CollectionViewType.Time;
+ alias._viewType = CollectionViewType.Time;
let list = Cast(alias._columnHeaders, listSpec(SchemaHeaderField));
if (!list) {
alias._columnHeaders = list = new List<SchemaHeaderField>();
@@ -191,7 +191,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
- this.props.tbox.props.addDocTab(alias, "onRight");
+ this.props.tbox.props.addDocTab(alias, "add:right");
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index afdd8fea2..160f4ba72 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -32,8 +32,8 @@
.formattedTextBox-dictation {
height: 12px;
width: 10px;
- top: 0px;
- left: 0px;
+ bottom: 5px;
+ right: 8px;
position: absolute;
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2ded33346..311143ff7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEqual } from "lodash";
import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx";
@@ -22,7 +20,7 @@ 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 { makeInterface } from "../../../../fields/Schema";
import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types";
import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
@@ -33,8 +31,8 @@ import { DocumentType } from '../../../documents/DocumentTypes';
import { DictationManager } from '../../../util/DictationManager';
import { DragManager } from "../../../util/DragManager";
import { makeTemplate } from '../../../util/DropConverter';
-import buildKeymap, { updateBullets } from "./ProsemirrorExampleTransfer";
-import RichTextMenu from './RichTextMenu';
+import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer";
+import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from "./RichTextRules";
//import { DashDocView } from "./DashDocView";
@@ -61,9 +59,6 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark }
import React = require("react");
import { DocumentManager } from '../../../util/DocumentManager';
-library.add(faEdit);
-library.add(faSmile, faTextHeight, faUpload);
-
export interface FormattedTextBoxProps {
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)
@@ -99,8 +94,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _linkTime: number | null = null;
private _pause: boolean = false;
- @computed get _recording() { return this.dataDoc.audioState === "recording"; }
- set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
+ @computed get _recording() { return this.dataDoc?.audioState === "recording"; }
+ set _recording(value) {
+ this.dataDoc.audioState = value ? "recording" : undefined;
+ }
@observable private _entered = false;
@@ -225,7 +222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
- const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value });
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -350,7 +347,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
updateTitle = () => {
if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
- StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
+ StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
+ Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey) {
let node = this._editorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
const str = node.textContent;
@@ -374,10 +372,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
- public highlightSearchTerms = (terms: string[], alt: boolean) => {
+ public highlightSearchTerms = (terms: string[], backward: boolean) => {
if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-
-
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
@@ -385,31 +381,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let tr = this._editorView.state.tr;
const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
- if (BoolCast(Doc.GetProto(this.dataDoc).resetSearch) === true) {
- this._searchIndex = 0;
- Doc.GetProto(this.dataDoc).resetSearch = undefined;
- }
- else {
- this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
- if (alt === true) {
- if (this._searchIndex > 1) {
- this._searchIndex += -2;
- }
- else if (this._searchIndex === 1) {
- this._searchIndex = length - 1;
- }
- else if (this._searchIndex === 0 && length !== 1) {
- this._searchIndex = length - 2;
- }
-
+ this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+ if (backward === true) {
+ if (this._searchIndex > 1) {
+ this._searchIndex += -2;
}
+ else if (this._searchIndex === 1) {
+ this._searchIndex = length - 1;
+ }
+ else if (this._searchIndex === 0 && length !== 1) {
+ this._searchIndex = length - 2;
+ }
+
}
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
-
- console.log(this._searchIndex);
}
}
@@ -472,12 +460,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
} else if (de.complete.linkDragData) {
de.complete.linkDragData.linkDropCallback = this.linkDrop;
}
+ else if (de.complete.annoDragData) {
+ de.complete.annoDragData.linkDropCallback = this.linkDrop;
+ }
}
- linkDrop = (data: DragManager.LinkDragData) => {
+ linkDrop = (data: { linkDocument?: Doc }) => {
const linkDoc = data.linkDocument!;
const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
- this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id);
+ this.makeLinkToSelection(linkDoc[Id], anchor1Title, "add:right", anchor1Id);
}
getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -511,10 +502,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (node.isTextblock) {
let index = 0, foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
- while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
- const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
- ret.push(sel);
- index = index + foundAt + find.length;
+ const regexp = new RegExp(find.replace("*", ""), "i");
+ if (regexp) {
+ while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
+ const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
+ ret.push(sel);
+ index = index + foundAt + find.length;
+ }
}
} else {
node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
@@ -664,7 +658,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0];
mainBulletList.push.apply(mainBulletList, newBullets);
}
- console.log(mainBulletList.length);
const title = Docs.Create.TextDocument(StrCast(this.rootDoc.title), { title: "Title", _width: 800, _height: 70, x: 20, y: -10, _fontSize: '20pt', backgroundColor: "rgba(0,0,0,0)", appearFrame: 0, _fontWeight: 700 });
mainBulletList.push(title);
const doc = Docs.Create.FreeformDocument(mainBulletList, {
@@ -719,7 +712,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
recordDictation = () => {
DictationManager.Controls.listen({
- interimHandler: this.setCurrentBulletContent,
+ interimHandler: this.setDictationContent,
continuous: { indefinite: false },
}).then(results => {
if (results && [DictationManager.Controls.Infringed].includes(results)) {
@@ -730,22 +723,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
- recordBullet = async () => {
- const completedCue = "end session";
- const results = await DictationManager.Controls.listen({
- interimHandler: this.setCurrentBulletContent,
- continuous: { indefinite: false },
- terminators: [completedCue, "bullet", "next"]
- });
- if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
- DictationManager.Controls.stop();
- return;
- }
- this.nextBullet(this._editorView!.state.selection.to);
- setTimeout(this.recordBullet, 2000);
- }
-
- setCurrentBulletContent = (value: string) => {
+ setDictationContent = (value: string) => {
if (this._editorView) {
const state = this._editorView.state;
const now = Date.now();
@@ -760,33 +738,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
- this._break = false;
- value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
const from = state.selection.from;
- const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
- this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
- }
- }
-
- nextBullet = (pos: number) => {
- if (this._editorView) {
- const frag = Fragment.fromArray(this.newListItems(2));
- if (this._editorView.state.doc.resolve(pos).depth >= 2) {
- const slice = new Slice(frag, 2, 2);
- let state = this._editorView.state;
- this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
- pos += 4;
- state = this._editorView.state;
- this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+ this._break = false;
+ if (this.props.Document.recordingStart) {
+ const recordingStart = DateCast(this.props.Document.recordingStart)?.date.getTime();
+ value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
}
+ const tr = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
+ this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, from + value.length + 1)));
}
}
- private newListItems = (count: number) => {
- return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
- }
-
_keymap: any = undefined;
_rules: RichTextRules | undefined;
@computed get config() {
@@ -852,13 +814,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (linkDoc) {
const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
- this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id);
+ this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
}
},
{ fireImmediately: true }
);
this._disposers.editorState = reaction(
() => {
+ if (!this.dataDoc || !this.layoutDoc) return undefined;
if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
}
@@ -894,13 +857,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
this._disposers.autoHeight = reaction(
- () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
- () => setTimeout(() => this.tryUpdateHeight(), 0)
+ () => ({
+ width: NumCast(this.layoutDoc._width),
+ autoHeight: this.layoutDoc?._autoHeight
+ }),
+ ({ width, autoHeight }) => width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0)
);
this._disposers.height = reaction(
- () => this.layoutDoc[HeightSym](),
+ () => NumCast(this.layoutDoc._height),
action(height => {
- if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
+ if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
this.layoutDoc._delayAutoHeight = height;
}
})
@@ -908,36 +874,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.setupEditor(this.config, this.props.fieldKey);
- this._disposers.searchAlt = reaction(() => this.rootDoc.searchMatchAlt,
- search => search ? this.highlightSearchTerms([Doc.SearchQuery()], false) : this.unhighlightSearchTerms(),
- { fireImmediately: true });
- this._disposers.search = reaction(() => this.rootDoc.searchMatch,
- search => search ? this.highlightSearchTerms([Doc.SearchQuery()], true) : this.unhighlightSearchTerms(),
- { fireImmediately: this.rootDoc.searchMatch ? true : false });
-
- this._disposers.record = reaction(() => this._recording,
- () => {
- if (this._recording) {
- setTimeout(action(() => {
- this.stopDictation(true);
- setTimeout(() => this.recordDictation(), 500);
- }), 500);
- } else setTimeout(() => this.stopDictation(true), 0);
- }
- );
+ this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
+ search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
+ { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
+
+ this._disposers.selected = reaction(() => this.props.isSelected(), action(() => this._recording = false));
+
+ if (!this.props.dontRegisterView) {
+ this._disposers.record = reaction(() => this._recording,
+ () => {
+ if (this._recording) {
+ setTimeout(action(() => {
+ this.stopDictation(true);
+ setTimeout(() => this.recordDictation(), 500);
+ }), 500);
+ } else setTimeout(() => this.stopDictation(true), 0);
+ }
+ );
+ }
this._disposers.scrollToRegion = reaction(
() => StrCast(this.layoutDoc.scrollToLinkID),
async (scrollToLinkID) => {
const findLinkFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
+ let offset = 0;
frag.forEach((node, index) => {
const examinedNode = findLinkNode(node, editor);
if (examinedNode?.textContent) {
nodes.push(examinedNode);
- start += index;
+ offset = index;
}
});
- return { frag: Fragment.fromArray(nodes), start: start };
+ return { frag: Fragment.fromArray(nodes), start: start + offset };
};
const findLinkNode = (node: Node, editor: EditorView) => {
if (!node.isText) {
@@ -949,7 +917,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
};
- let start = 0;
+ const start = 0;
if (this._editorView && scrollToLinkID) {
const editor = this._editorView;
const ret = findLinkFrag(editor.state.doc.content, editor);
@@ -1036,7 +1004,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}, 0);
dataDoc.title = exportState.title;
- this.rootDoc.customTitle = true;
+ this.dataDoc["title-custom"] = true;
dataDoc.unchanged = true;
} else {
delete dataDoc[GoogleRef];
@@ -1131,7 +1099,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
- const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
@@ -1278,10 +1246,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBoxComment.Hide();
if (FormattedTextBoxComment.linkDoc) {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
} else {
DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
- (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
}
}
@@ -1307,7 +1275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// jump rich text menu to this textbox
const bounds = this._ref.current?.getBoundingClientRect();
- if (bounds && this.layoutDoc._chromeStatus !== "disabled") {
+ if (bounds && this.layoutDoc._chromeStatus !== "disabled" && RichTextMenu.Instance) {
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) {
@@ -1411,11 +1379,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ menuPlugin: any;
+
richTextMenuPlugin() {
+ const self = this;
return new Plugin({
view(newView) {
- RichTextMenu.Instance?.changeView(newView);
- return RichTextMenu.Instance;
+ self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ return self.menuPlugin = new RichTextMenuPlugin({ editorProps: this.props });
}
});
}
@@ -1432,13 +1403,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
return wasUndoing;
}
+ public static LiveTextUndo: UndoManager.Batch | undefined;
public static HadSelection: boolean = false;
onBlur = (e: any) => {
FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
- //DictationManager.Controls.stop(false);
this.endUndoTypingBatch();
this.doLinkOnDeselect();
+ FormattedTextBox.LiveTextUndo?.end();
+ FormattedTextBox.LiveTextUndo = undefined;
// move the richtextmenu offscreen
//if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide();
}
@@ -1524,8 +1497,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
TraceMobx();
const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
- const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
- setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected
+ const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground;
+ this.props.isSelected() && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props), 0); // need to make sure that we update a text box that is selected after updating the one that was deselected
if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
setTimeout(() => FormattedTextBoxComment.Hide(), 0);
}
@@ -1546,13 +1519,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
width: "100%",
height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : 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 ? undefined : "none",
fontSize: Cast(this.layoutDoc._fontSize, "string", null),
fontWeight: Cast(this.layoutDoc._fontWeight, "number", null),
fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
- transition: "opacity 1s"
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyPress}
@@ -1616,14 +1587,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
<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();
- }} >
+ <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
<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" />
+ style={{
+ color: this._recording ? "red" : "blue",
+ transitionDelay: "0.6s",
+ opacity: this._recording ? 1 : 0.25,
+ }}
+ icon={"microphone"} size="sm" />
</div>}
</div>
</div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 6f3984f39..b4f648273 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc";
import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter, returnEmptyDoclist } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
import { schema } from "./schema_rts";
@@ -103,7 +103,7 @@ export class FormattedTextBoxComment {
this.deleteLink();
} else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
} else {
const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ?
Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc))
@@ -111,29 +111,29 @@ export class FormattedTextBoxComment {
const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
if (FormattedTextBoxComment.linkDoc.follow) {
- if (FormattedTextBoxComment.linkDoc.follow === "Default") {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+ if (FormattedTextBoxComment.linkDoc.follow === "default") {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false);
} else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") {
- if (target) { textBox.props.addDocTab(target, "onRight"); }
+ if (target) { textBox.props.addDocTab(target, "add:right"); }
} else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") {
- if (target) { textBox.props.addDocTab(target, "inTab"); }
+ if (target) { textBox.props.addDocTab(target, "add"); }
}
} else {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false);
}
}
} else {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
} else {
DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "add" : 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, UseCors: true }), "onRight");
+ textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, useCors: true }), "add:right");
}
keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
@@ -232,6 +232,16 @@ export class FormattedTextBoxComment {
const mark = child ? findLinkMark(child.marks) : undefined;
const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl;
if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
+ try {
+ ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+ } catch (e) { }
+ FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
+ FormattedTextBoxComment.tooltipText = document.createElement("div");
+ FormattedTextBoxComment.tooltipText.style.width = "100%";
+ FormattedTextBoxComment.tooltipText.style.height = "100%";
+ FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
+ FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+
FormattedTextBoxComment.tooltipText.textContent = "external => " + href;
(FormattedTextBoxComment.tooltipText as any).href = href;
if (href.startsWith("https://en.wikipedia.org/wiki/")) {
@@ -241,12 +251,9 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
}
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+ const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
FormattedTextBoxComment.tooltipText.textContent = "target not found...";
(FormattedTextBoxComment.tooltipText as any).href = "";
- const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- try {
- ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
- } catch (e) { }
docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
if (linkDoc instanceof Doc) {
(FormattedTextBoxComment.tooltipText as any).href = href;
@@ -302,6 +309,7 @@ export class FormattedTextBoxComment {
pinToPres={returnFalse}
dontRegisterView={true}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={0}
@@ -323,8 +331,8 @@ export class FormattedTextBoxComment {
ReactDOM.render(docPreview, 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 = "100%";
+ FormattedTextBoxComment.tooltip.style.height = "100%";
}
}
});
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 8faf752b4..c6bacc1a8 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -33,7 +33,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
return tx2;
};
-export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
const keys: { [key: string]: any } = {};
function bind(key: string, cmd: any) {
@@ -104,7 +104,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
//Command to create a new Tab with a PDF of all the command shortcuts
bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _width: 300, _height: 300 });
- props.addDocTab(newDoc, "onRight");
+ props.addDocTab(newDoc, "add:right");
});
//Commands to modify BlockType
@@ -143,7 +143,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Doc.MakeCopy(originalDoc, true);
newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined;
- newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
+ newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
@@ -168,7 +168,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Doc.MakeCopy(originalDoc, true);
newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined;
- newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
+ newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index b683fb25d..307238ea1 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,8 +1,8 @@
import React = require("react");
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, IReactionDisposer, reaction } from "mobx";
+import { Tooltip } from "@material-ui/core";
+import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { lift, wrapIn } from "prosemirror-commands";
import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
@@ -11,31 +11,28 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc } from "../../../../fields/Doc";
import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";
+import { Cast, StrCast } from "../../../../fields/Types";
+import { TraceMobx } from "../../../../fields/util";
import { unimplementedFunction, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { LinkManager } from "../../../util/LinkManager";
import { SelectionManager } from "../../../util/SelectionManager";
-import AntimodeMenu from "../../AntimodeMenu";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
import { FieldViewProps } from "../FieldView";
import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
import { updateBullets } from "./ProsemirrorExampleTransfer";
import "./RichTextMenu.scss";
import { schema } from "./schema_rts";
-import { TraceMobx } from "../../../../fields/util";
-import { UndoManager, undoBatch } from "../../../util/UndoManager";
-import { Tooltip } from "@material-ui/core";
const { toggleMark } = require("prosemirror-commands");
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
-
@observer
-export default class RichTextMenu extends AntimodeMenu {
+export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: RichTextMenu;
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
- private view?: EditorView;
+ public view?: EditorView;
public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@@ -78,7 +75,7 @@ export default class RichTextMenu extends AntimodeMenu {
RichTextMenu.Instance = this;
this._canFade = false;
//this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
- this.Pinned = true;
+ runInAction(() => this.Pinned = true);
this.fontSizeOptions = [
{ mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
@@ -156,22 +153,8 @@ export default class RichTextMenu extends AntimodeMenu {
public delayHide = () => this._delayHide = true;
@action
- changeView(view: EditorView) {
- if ((view as any)?.TextView?.props.isSelected(true)) {
- this.view = view;
- }
- }
-
- update(view: EditorView, lastState: EditorState | undefined) {
- RichTextMenu.Instance.updateFromDash(view, lastState, this.editorProps);
- }
-
- @action
- public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
- RichTextMenu.Instance.finalUpdateFromDash(view, lastState, props);
- }
- public async finalUpdateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
- if (!view || !(view as any).TextView?.props.isSelected(true)) {
+ public updateMenu(view: EditorView, lastState: EditorState | undefined, props: any) {
+ if (!view || !(view as any).TextView?.props.isSelected(true) || !view.hasFocus()) {
return;
}
this.view = view;
@@ -199,8 +182,7 @@ export default class RichTextMenu extends AntimodeMenu {
this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "...";
// update link in current selection
- const targetTitle = await this.getTextLinkTargetTitle();
- this.setCurrentLink(targetTitle);
+ this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
}
setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => {
@@ -268,7 +250,9 @@ export default class RichTextMenu extends AntimodeMenu {
const pos = this.view.state.selection.$from;
const ref_node = this.reference_node(pos);
if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
- ref_node.marks.forEach(m => {
+ const marks = Array.from(ref_node.marks);
+ marks.push(...(this.view.state.storedMarks as any));
+ marks.forEach(m => {
m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color);
m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
@@ -443,14 +427,20 @@ export default class RichTextMenu extends AntimodeMenu {
if ((this.view?.state.selection.$from.pos || 0) < 2) {
this.TextView.layoutDoc._fontSize = mark.attrs.fontSize;
}
- this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch, true);
+ const fmark = view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize });
+ this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
+ view.focus();
+ this.updateMenu(view, undefined, this.props);
}
changeFontFamily = (mark: Mark, view: EditorView) => {
if ((this.view?.state.selection.$from.pos || 0) < 2) {
this.TextView.layoutDoc._fontFamily = mark.attrs.family;
}
- this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch, true);
+ const fmark = view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family });
+ this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
+ view.focus();
+ this.updateMenu(view, undefined, this.props);
}
// TODO: remove doesn't work
@@ -486,6 +476,8 @@ export default class RichTextMenu extends AntimodeMenu {
this.view.dispatch(tx3);
}
}
+ this.view.focus();
+ this.updateMenu(this.view, undefined, this.props);
}
insertSummarizer(state: EditorState<any>, dispatch: any) {
@@ -690,16 +682,22 @@ export default class RichTextMenu extends AntimodeMenu {
e.preventDefault();
e.stopPropagation();
self.TextView.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
- self.TextView.EditorView!.focus();
+ if (self.view) {
+ UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
+ self.view.focus();
+ self.updateMenu(self.view, undefined, self.props);
+ }
}
function changeColor(e: React.PointerEvent, color: string) {
e.preventDefault();
e.stopPropagation();
self.setActiveColor(color);
self.TextView.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
- self.TextView.EditorView!.focus();
+ if (self.view) {
+ UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
+ self.view.focus();
+ self.updateMenu(self.view, undefined, self.props);
+ }
}
// onPointerDown={onColorClick}
@@ -812,7 +810,7 @@ export default class RichTextMenu extends AntimodeMenu {
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+ <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
<div className="divider"></div>
<button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
</div>;
@@ -859,7 +857,7 @@ export default class RichTextMenu extends AntimodeMenu {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target);
}
@undoBatch
@@ -988,7 +986,7 @@ export default class RichTextMenu extends AntimodeMenu {
{[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)),
this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)),
<div className="richTextMenu-divider" key="divider 4" />,
- this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", action((val: string) => this.activeListType = val)),
+ this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
@@ -1020,6 +1018,7 @@ interface ButtonDropdownProps {
dropdownContent: JSX.Element;
openDropdownOnButton?: boolean;
link?: boolean;
+ pdf?: boolean;
}
@observer
@@ -1059,15 +1058,33 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
}, 0);
}
+
render() {
return (
<div className="button-dropdown-wrapper" ref={node => this.ref = node}>
- <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
- {this.props.button}
- <div style={{ marginTop: "-8.5" }}><FontAwesomeIcon icon="caret-down" size="sm" /></div>
- </div>
+ {!this.props.pdf ?
+ <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
+ {this.props.button}
+ <div style={{ marginTop: "-8.5" }}><FontAwesomeIcon icon="caret-down" size="sm" /></div>
+ </div>
+ :
+ <>
+ {this.props.button}
+ <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </button>
+ </>}
{this.showDropdown ? this.props.dropdownContent : (null)}
</div>
);
}
+}
+
+
+interface RichTextMenuPluginProps {
+ editorProps: any;
+}
+export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
+ render() { return null; }
+ update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); }
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index dc1d8a2c8..5c0505909 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -3,13 +3,13 @@ import { NodeSelection, TextSelection } from "prosemirror-state";
import { DataSym, Doc } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { ComputedField } from "../../../../fields/ScriptField";
-import { Cast, NumCast } from "../../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../../fields/Types";
import { returnFalse, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
import { FormattedTextBox } from "./FormattedTextBox";
import { wrappingInputRule } from "./prosemirrorPatches";
-import RichTextMenu from "./RichTextMenu";
+import { RichTextMenu } from "./RichTextMenu";
import { schema } from "./schema_rts";
import { List } from "../../../../fields/List";
@@ -92,7 +92,7 @@ export class RichTextRules {
const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" });
textDocInline.title = inlineFieldKey; // give the annotation its own title
- textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+ textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
@@ -279,7 +279,7 @@ export class RichTextRules {
DocUtils.Publish(target, docid, returnFalse, returnFalse);
DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
});
- const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+ const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "add:right", title: docid, targetId: docid });
return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
}
return state.tr;
@@ -321,7 +321,11 @@ export class RichTextRules {
(state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- this.Document[DataSym]["#" + tag] = ".";
+ this.Document[DataSym]["#" + tag] = "#" + tag;
+ const tags = StrCast(this.Document.tags, ":");
+ if (!tags.includes(`#${tag}:`)) {
+ this.Document[DataSym].tags = `"${tags + "#" + tag + ':'}"`;
+ }
const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
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 33a080fe4..f0bacb735 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -13,6 +13,7 @@ import { Transform } from "../../../util/Transform";
import { DocumentView } from "../DocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
import React = require("react");
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
export class DashDocView {
@@ -43,7 +44,7 @@ export class DashDocView {
this._outer = document.createElement("span");
this._outer.style.position = "relative";
this._outer.style.textIndent = "0";
- this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+ this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
this._outer.style.width = node.attrs.width;
this._outer.style.height = node.attrs.height;
this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
@@ -126,7 +127,7 @@ export class DashDocView {
this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => {
this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px";
this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
- this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+ this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
}, { fireImmediately: true });
const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
@@ -155,6 +156,7 @@ export class DashDocView {
bringToFront={emptyFunction}
dontRegisterView={false}
docFilters={this._textBox.props.docFilters}
+ searchFilterDocs={this._textBox.props.searchFilterDocs}
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
ContentScaling={this.contentScaling}
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 1616500f6..64f7d27e5 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -148,7 +148,7 @@ export const nodes: { [index: string]: NodeSpec } = {
alt: { default: null },
title: { default: null },
float: { default: "left" },
- location: { default: "onRight" },
+ location: { default: "add:right" },
docid: { default: "" }
},
group: "inline",
@@ -177,7 +177,7 @@ export const nodes: { [index: string]: NodeSpec } = {
height: { default: 100 },
title: { default: null },
float: { default: "right" },
- location: { default: "onRight" },
+ location: { default: "add:right" },
hidden: { default: false },
fieldKey: { default: "" },
docid: { default: "" },
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index d29b638e6..222a6cb0f 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -4,9 +4,9 @@ import { observer } from "mobx-react";
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 { Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../fields/Types";
import { DocumentManager } from "../../util/DocumentManager";
-import PDFMenu from "./PDFMenu";
+import { PDFMenu } from "./PDFMenu";
import "./Annotation.scss";
interface IAnnotationProps {
@@ -19,7 +19,7 @@ interface IAnnotationProps {
}
@observer
-export default
+export
class Annotation extends React.Component<IAnnotationProps> {
render() {
return DocListCast(this.props.anno.annotations).map(a => (
@@ -86,7 +86,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
@action
- onPointerDown = async (e: React.PointerEvent) => {
+ onPointerDown = (e: React.PointerEvent) => {
if (e.button === 2 || e.ctrlKey) {
PDFMenu.Instance.Status = "annotation";
PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this);
@@ -97,11 +97,11 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
e.stopPropagation();
}
else if (e.button === 0) {
- const annoGroup = await Cast(this.props.document.group, Doc);
- if (annoGroup) {
- DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
- e.stopPropagation();
- }
+ e.persist();
+ e.stopPropagation();
+ PromiseValue(this.props.document.group).then(annoGroup => annoGroup instanceof Doc &&
+ DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined)
+ );
}
}
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 7bea8d01b..32dd376ac 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -1,17 +1,17 @@
import React = require("react");
-import "./PDFMenu.scss";
-import { observable, action, computed, } from "mobx";
-import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { unimplementedFunction, returnFalse, Utils } from "../../../Utils";
-import AntimodeMenu from "../AntimodeMenu";
-import { Doc, Opt } from "../../../fields/Doc";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
import { ColorState } from "react-color";
+import { Doc, Opt } from "../../../fields/Doc";
+import { returnFalse, unimplementedFunction, Utils } from "../../../Utils";
+import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
+import "./PDFMenu.scss";
@observer
-export default class PDFMenu extends AntimodeMenu {
+export class PDFMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: PDFMenu;
private _commentCont = React.createRef<HTMLButtonElement>();
@@ -112,7 +112,7 @@ export default class PDFMenu extends AntimodeMenu {
</div>
</div>;
return (
- <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
);
}
@@ -154,7 +154,7 @@ export default class PDFMenu extends AntimodeMenu {
const buttons = this.Status === "pdf" ?
[
this.highlighter,
- <button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
+ <button key="2" className="antimodeMenu-button annotate" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: "grab" }}>
<FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
] : [
<button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f9ae78778..18be9b679 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,42 +1,43 @@
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym, AclAddonly, AclEdit, AclAdmin, DataSym } from "../../../fields/Doc";
+import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, 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, listSpec } from "../../../fields/Schema";
-import { ScriptField, ComputedField } from "../../../fields/ScriptField";
+import { createSchema, makeInterface } from "../../../fields/Schema";
+import { ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
import { PdfField } from "../../../fields/URLField";
-import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
+import { GetEffectiveAcl, 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";
+import { Networking } from "../../Network";
import { DragManager } from "../../util/DragManager";
import { CompiledScript, CompileScript } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
+import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionView } from "../collections/CollectionView";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
-import Annotation from "./Annotation";
-import PDFMenu from "./PDFMenu";
+import { Annotation } from "./Annotation";
+import { PDFMenu } from "./PDFMenu";
import "./PDFViewer.scss";
+const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
import React = require("react");
-import { SnappingManager } from "../../util/SnappingManager";
+import { LinkDocPreview } from "../nodes/LinkDocPreview";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
-import { Networking } from "../../Network";
export const pageSchema = createSchema({
- curPage: "number",
+ _curPage: "number",
rotation: "number",
scrollHeight: "number",
serachMatch: "boolean"
@@ -56,6 +57,7 @@ interface IViewerProps {
Document: Doc;
DataDoc?: Doc;
docFilters: () => string[];
+ searchFilterDocs: () => Doc[];
ContainingCollectionView: Opt<CollectionView>;
PanelWidth: () => number;
PanelHeight: () => number;
@@ -97,16 +99,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@observable private _zoomed = 1;
private _pdfViewer: any;
+ private _styleRule: any; // stylesheet rule for making hyperlinks clickable
private _retries = 0; // number of times tried to create the PDF viewer
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- private _reactionDisposer?: IReactionDisposer;
- private _selectionReactionDisposer?: IReactionDisposer;
- private _annotationReactionDisposer?: IReactionDisposer;
- private _scrollTopReactionDisposer?: IReactionDisposer;
- private _filterReactionDisposer?: IReactionDisposer;
- private _searchReactionDisposer?: IReactionDisposer;
- private _searchReactionDisposer2?: IReactionDisposer;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _selectionText: string = "";
@@ -134,7 +131,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
const coreFilename = pathComponents.pop()!.split(".")[0];
const params: any = {
coreFilename,
- pageNum: this.Document.curPage || 1,
+ pageNum: this.Document._curPage || 1,
};
if (pathComponents.length) {
params.subtree = `${pathComponents.join("/")}/`;
@@ -143,20 +140,20 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
} else {
const params: any = {
coreFilename: relative.split("/")[relative.split("/").length - 1],
- pageNum: this.Document.curPage || 1,
+ pageNum: this.Document._curPage || 1,
};
this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
}
runInAction(() => this._showWaiting = this._showCover = true);
this.props.startupLive && this.setupPdfJsViewer();
this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0);
- this._searchReactionDisposer = reaction(() => this.Document.searchMatch,
+ this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc),
m => {
- if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), true);
+ if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), m.searchMatch > 0);
else !(this._lastSearch = false) && setTimeout(() => !this._lastSearch && this.search("", false, true), 200);
}, { fireImmediately: true });
- this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
+ this._disposers.selected = reaction(() => this.props.isSelected(),
selected => {
if (!selected) {
this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
@@ -166,26 +163,26 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
(SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer();
},
{ fireImmediately: true });
- this._reactionDisposer = reaction(
+ this._disposers.scrollY = reaction(
() => this.Document._scrollY,
(scrollY) => {
if (scrollY !== undefined) {
(this._showCover || this._showWaiting) && this.setupPdfJsViewer();
- this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0));
+ (!LinkDocPreview.TargetDoc) && this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0));
setTimeout(() => this.Document._scrollY = undefined, 1000);
}
},
{ fireImmediately: true }
);
+ this._disposers.curPage = reaction(
+ () => this.Document._curPage,
+ (page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
+ { fireImmediately: true }
+ );
}
componentWillUnmount = () => {
- this._reactionDisposer?.();
- this._scrollTopReactionDisposer?.();
- this._annotationReactionDisposer?.();
- this._filterReactionDisposer?.();
- this._selectionReactionDisposer?.();
- this._searchReactionDisposer?.();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
document.removeEventListener("copy", this.copy);
}
@@ -226,11 +223,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this.props.setPdfViewer(this);
await this.initialLoad();
- this._scrollTopReactionDisposer = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
+ this._disposers.scrollTop = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
(stop) => (stop !== undefined && this.layoutDoc._scrollY === undefined && this._mainCont.current) && (this._mainCont.current.scrollTop = stop),
{ fireImmediately: true });
- this._filterReactionDisposer = reaction(
+ this._disposers.filterScript = reaction(
() => Cast(this.Document.filterScript, ScriptField),
action(scriptField => {
const oldScript = this._script.originalScript;
@@ -245,8 +242,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
pagesinit = action(() => {
- this._pdfViewer.currentScaleValue = this._zoomed = 1;
- this.gotoPage(this.Document.curPage || 1);
+ if (this._pdfViewer._setDocumentViewerElement.offsetParent) {
+ this._pdfViewer.currentScaleValue = this._zoomed = 1;
+ this.gotoPage(this.Document._curPage || 1);
+ }
document.removeEventListener("pagesinit", this.pagesinit);
});
@@ -313,7 +312,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
annoDocs.push(annoDoc);
anno.remove();
(annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY));
- (annoDoc.x !== undefined) && (maxX = Math.max(NumCast(annoDoc.x) + NumCast(annoDoc.width), maxX));
+ (annoDoc.x !== undefined) && (maxX = Math.max(NumCast(annoDoc.x) + NumCast(annoDoc._width), maxX));
}));
mainAnnoDocProto.y = Math.max(minY, 0);
@@ -342,7 +341,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
gotoPage = (p: number) => {
- this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
+ if (this._pdfViewer?._setDocumentViewerElement?.offsetParent) {
+ this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
+ }
}
@action
@@ -363,7 +364,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onScroll = (e: React.UIEvent<HTMLElement>) => {
this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop);
- this._pdfViewer && (this.Document.curPage = this._pdfViewer.currentPageNumber);
+ this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber);
}
// get the page index that the vertical offset passed in is on
@@ -436,11 +437,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
- addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
+ (e.target as any).tagName === "SPAN" && (this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" }));
if ((this.Document._viewScale || 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this.active(true)) {
this._setPreviewCursor?.(e.clientX, e.clientY, true);
- //e.stopPropagation();
}
this._marqueeing = false;
if (!e.altKey && e.button === 0 && this.active(true)) {
@@ -462,12 +462,15 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this._marqueeHeight = this._marqueeWidth = 0;
this._marqueeing = true;
}
- document.removeEventListener("pointermove", this.onSelectMove);
document.addEventListener("pointermove", this.onSelectMove);
- document.removeEventListener("pointerup", this.onSelectEnd);
document.addEventListener("pointerup", this.onSelectEnd);
+ document.addEventListener("pointerup", this.removeStyle, true);
}
}
+ removeStyle = () => {
+ clearStyleSheetRules(PDFViewer._annotationStyle);
+ document.removeEventListener("pointerup", this.removeStyle);
+ }
@action
onSelectMove = (e: PointerEvent): void => {
@@ -602,15 +605,15 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
// Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
// const snipLayout = Docs.Create.PdfDocument("http://www.msn.com", { title: "snippetView", isTemplateDoc: true, isTemplateForField: "snipped", _fitWidth: true, _width: this.marqueeWidth(), _height: this.marqueeHeight(), _scrollTop: this.marqueeY() });
// Doc.GetProto(snipLayout).layout = PDFBox.LayoutString("snipped");
- const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
+ const annotationDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
if (annotationDoc) {
DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
dragComplete: e => {
- if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
- const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
- annotationDoc.isLinkButton = true;
- if (link) link.followLinkLocation = "onRight";
+ if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) {
+ e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+ annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing?
}
+ e.annoDragData && e.annoDragData.linkDocument && e.annoDragData?.linkDropCallback?.({ linkDocument: e.annoDragData.linkDocument });
}
});
}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index a25a8ee33..c33637d0f 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -20,6 +20,7 @@ import { DocumentType } from "../../documents/DocumentTypes";
import { Tooltip } from "@material-ui/core";
import { DragManager } from "../../util/DragManager";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { undoBatch } from "../../util/UndoManager";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -102,6 +103,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
bringToFront={returnFalse}
opacity={returnOne}
docFilters={this.props.docFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={returnOne}
@@ -205,6 +207,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
}
}
+ @undoBatch
+ removeItem = action((e: React.MouseEvent) => {
+ this.props.removeDocument?.(this.rootDoc);
+ if (PresBox.Instance._selectedArray.includes(this.rootDoc)) {
+ PresBox.Instance._selectedArray.splice(PresBox.Instance._selectedArray.indexOf(this.rootDoc), 1);
+ }
+ e.stopPropagation();
+ });
+
render() {
const className = "presElementBox-item" + (PresBox.Instance._selectedArray.includes(this.rootDoc) ? " presElementBox-active" : "");
const pbi = "presElementBox-interaction";
@@ -253,10 +264,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
<Tooltip title={<><div className="dash-tooltip">{"Presentation pin view"}</div></>}><div className="presElementBox-time" style={{ fontWeight: 700, display: this.rootDoc.presPinView && PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>V</div></Tooltip>
<Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div
className="presElementBox-closeIcon"
- onClick={e => {
- this.props.removeDocument?.(this.rootDoc);
- e.stopPropagation();
- }}>
+ onClick={this.removeItem}>
<FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
</div></Tooltip>
<Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"presElementBox-expand" + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}>
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 9b7cf2fc6..f1dd106a7 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -1,28 +1,11 @@
-import * as React from 'react';
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
+import { DocumentType } from "../../documents/DocumentTypes";
// import "./SearchBox.scss";
import "./IconBar.scss";
-import "./IconButton.scss";
-import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faTimesCircle, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import * as _ from "lodash";
import { IconButton } from './IconButton';
-import { DocumentType } from "../../documents/DocumentTypes";
-
-
-library.add(faSearch);
-library.add(faObjectGroup);
-library.add(faImage);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-library.add(faMusic);
-library.add(faLink);
-library.add(faChartBar);
-library.add(faGlobeAsia);
-library.add(faBan);
+import "./IconButton.scss";
export interface IconBarProps {
setIcons: (icons: string[]) => void;
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 52641c543..349690b20 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -1,30 +1,14 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
-import "./SearchBox.scss";
-import "./IconButton.scss";
-import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library, icon } from '@fortawesome/fontawesome-svg-core';
+import * as _ from "lodash";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import { DocumentType } from "../../documents/DocumentTypes";
import '../globalCssVariables.scss';
-import * as _ from "lodash";
import { IconBar } from './IconBar';
-import { props } from 'bluebird';
-import { Search } from '../../../server/Search';
-import { gravity } from 'sharp';
-
-library.add(faSearch);
-library.add(faObjectGroup);
-library.add(faImage);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-library.add(faMusic);
-library.add(faLink);
-library.add(faChartBar);
-library.add(faGlobeAsia);
-library.add(faBan);
+import "./IconButton.scss";
+import "./SearchBox.scss";
+import { Font } from '@react-pdf/renderer';
interface IconButtonProps {
type: string;
@@ -47,59 +31,46 @@ export class IconButton extends React.Component<IconButtonProps>{
componentDidMount = () => {
this._resetReaction = reaction(
() => IconBar.Instance._resetClicked,
- () => {
+ action(() => {
if (IconBar.Instance._resetClicked) {
- runInAction(() => {
- this.reset();
- IconBar.Instance._reset++;
- if (IconBar.Instance._reset === 9) {
- IconBar.Instance._reset = 0;
- IconBar.Instance._resetClicked = false;
- }
- });
+ this._isSelected = false;
+ IconBar.Instance._reset++;
+ if (IconBar.Instance._reset === 9) {
+ IconBar.Instance._reset = 0;
+ IconBar.Instance._resetClicked = false;
+ }
}
- },
+ }),
);
+
this._selectAllReaction = reaction(
() => IconBar.Instance._selectAllClicked,
- () => {
+ action(() => {
if (IconBar.Instance._selectAllClicked) {
- runInAction(() => {
- this.select();
- IconBar.Instance._select++;
- if (IconBar.Instance._select === 9) {
- IconBar.Instance._select = 0;
- IconBar.Instance._selectAllClicked = false;
- }
- });
+ this._isSelected = true;
+ IconBar.Instance._select++;
+ if (IconBar.Instance._select === 9) {
+ IconBar.Instance._select = 0;
+ IconBar.Instance._selectAllClicked = false;
+ }
}
- },
+ }),
);
}
@action.bound
getIcon() {
switch (this.props.type) {
- case (DocumentType.NONE):
- return faBan;
- case (DocumentType.AUDIO):
- return faMusic;
- case (DocumentType.COL):
- return faObjectGroup;
- case (DocumentType.IMG):
- return faImage;
- case (DocumentType.LINK):
- return faLink;
- case (DocumentType.PDF):
- return faFilePdf;
- case (DocumentType.RTF):
- return faStickyNote;
- case (DocumentType.VID):
- return faVideo;
- case (DocumentType.WEB):
- return faGlobeAsia;
- default:
- return faCaretDown;
+ case (DocumentType.NONE): return "ban";
+ case (DocumentType.AUDIO): return "music";
+ case (DocumentType.COL): return "object-group";
+ case (DocumentType.IMG): return "image";
+ case (DocumentType.LINK): return "link";
+ case (DocumentType.PDF): return "file-pdf";
+ case (DocumentType.RTF): return "sticky-note";
+ case (DocumentType.VID): return "video";
+ case (DocumentType.WEB): return "globe-asia";
+ default: return "caret-down";
}
}
@@ -136,53 +107,16 @@ export class IconButton extends React.Component<IconButtonProps>{
//backgroundColor: "rgb(178, 206, 248)" //$darker-alt-accent
};
- @action.bound
- public reset() { this._isSelected = false; }
-
- @action.bound
- public select() { this._isSelected = true; }
-
- @action
- onMouseLeave = () => { this._hover = false; }
-
- @action
- onMouseEnter = () => { this._hover = true; }
-
- getFA = () => {
- switch (this.props.type) {
- case (DocumentType.NONE):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faBan} />);
- case (DocumentType.AUDIO):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />);
- case (DocumentType.COL):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />);
- case (DocumentType.IMG):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />);
- case (DocumentType.LINK):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />);
- case (DocumentType.PDF):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />);
- case (DocumentType.RTF):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />);
- case (DocumentType.VID):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />);
- case (DocumentType.WEB):
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faGlobeAsia} />);
- default:
- return (<FontAwesomeIcon className="fontawesome-icon" icon={faCaretDown} />);
- }
- }
-
render() {
return (
<div className="type-outer" id={this.props.type + "-filter"}
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}
+ onMouseEnter={() => this._hover = true}
+ onMouseLeave={() => this._hover = false}
onClick={this.onClick}>
<div className="type-icon" id={this.props.type + "-icon"}
style={this._hover ? this.hoverStyle : this._isSelected ? this.selected : this.notSelected}
>
- {this.getFA()}
+ <FontAwesomeIcon className="fontawesome-icon" icon={this.getIcon()} />
</div>
{/* <div className="filter-description">{this.props.type}</div> */}
</div>
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 3f06ba7d3..13d4a6d5a 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -6,71 +6,132 @@
flex-direction: column;
width: 100%;
height: 100%;
- position: absolute;
+ position: relative;
font-size: 10px;
line-height: 1;
overflow-y: auto;
overflow-x: visible;
- background: lightgrey,
-}
+ background: lightgrey;
+ overflow: visible;
+ z-index: 10000;
-.searchBox-bar {
- height: 32px;
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: black;
- .searchBox-barChild {
+ .searchBox-bar {
+ height: $searchpanel-height;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: black;
- &.searchBox-collection {
- flex: 0 1 auto;
- margin-left: 2px;
- margin-right: 2px
- }
+ .searchBox-lozenges {
+ position: absolute;
+ left: 15;
+ display: flex;
- &.searchBox-input {
- margin:5px;
- border-radius:20px;
- border:black;
- display: block;
- width: 130px;
- -webkit-transition: width 0.4s;
- transition: width 0.4s;
- align-self: stretch;
- outline:none;
+ .searchBox-lozenge-user,
+ .searchBox-lozenge-dashboard,
+ .searchBox-lozenge {
+ background-color: #313131;
+ border-radius: 5px;
+ height: 18px;
+ padding: 4px;
+ box-shadow: lightgrey 0.15em 0.15em 0.1em;
+ margin: 2px;
+ margin-bottom: 4px;
+ border-top: dimgrey 1px solid;
+ border-left: dimgrey 1px solid;
+ display: flex;
+ .searchBox-logoff,
+ .searchBox-dashboards {
+ border-radius: 3px;
+ background: olivedrab;
+ color: white;
+ position: relative;
+ display: none;
+ margin-left: 3px;
+ padding-left: 2px;
+ padding-right: 2px;
+ padding-bottom: 11px;
+ cursor: default;
+ }
+ .searchBox-logoff {
+ background: red;
+ }
+
+ .searchBox-dashSelect{
+ background-color: black;
+ color: white;
+ font-size: 9;
+ margin-right: 6;
+ border-radius: 5px;
+ position: relative;
+ height: 15px;
+ transform: translate(0,-3px);
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+ .searchBox-lozenge-user:hover {
+ .searchBox-logoff {
+ display:inline-block;
+ }
+ }
+ .searchBox-lozenge-dashboard:hover {
+ .searchBox-dashboards {
+ display:inline-block;
+ }
+ }
}
-
- .searchBox-input:focus {
- width: 500px;
- outline:none;
+ .searchBox-query {
+ position: relative;
+ display: flex;
+ width: 450;
}
+ .searchBox-barChild {
- &.searchBox-filter {
- align-self: stretch;
- button{
- transform:none;
+ &.searchBox-collection {
+ flex: 0 1 auto;
+ margin-left: 2px;
+ margin-right: 2px
}
- button:hover{
- transform:none;
+
+ &.searchBox-input {
+ margin:5px;
+ border-radius:20px;
+ border:black;
+ display: block;
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ align-self: stretch;
+ outline:none;
+ &:focus {
+ width: 500px;
+ outline:none;
+ }
+ }
+ &.searchBox-filter {
+ align-self: stretch;
+ button{
+ transform:none;
+ &:hover {
+ transform: none;
+ }
+ }
}
- }
- &.searchBox-submit {
- margin-left: 2px;
- margin-right: 2px
- }
+ &.searchBox-submit {
+ margin-left: 2px;
+ margin-right: 2px
+ }
- &.searchBox-close {
- color: $light-color;
- max-height: 32px;
+ &.searchBox-close {
+ color: $light-color;
+ max-height: $searchpanel-height;
+ }
}
- }
-}
-
-.searchBox-quickFilter {
- width: 100%;
- height: 40px;
- margin-top: 10px;
+ }
}
.searchBox-results {
@@ -91,241 +152,4 @@
text-align: left;
font-weight: bold;
}
-}
-
-.filter-form {
- position: relative;
- background: #121721;
- flex-direction: column;
- transform-origin: top;
- transition: height 0.3s ease, display 0.6s ease, overflow 0.6s ease;
- height:0px;
- overflow:hidden;
-
-
- .filter-header {
- //display: flex;
- position: relative;
- //flex-wrap:wrap;
- right: 1px;
- color: grey;
- //flex-direction: row-reverse;
- transform-origin: top;
- //justify-content: space-evenly;
- margin-bottom: 5px;
- overflow:hidden;
- transition:height 0.3s ease-out;
-
-
-
- .filter-item {
- position: relative;
- border:1px solid grey;
- border-radius: 16px;
-
- }
- }
-
- .filter-body {
- position: relative;
- right: 1px;
- color: grey;
- transform-origin: top;
- border-top: 0px;
-
- overflow:hidden;
- transition:height 0.3s ease-out;
- height:0px;
-
- }
- .filter-key {
- position: relative;
- right: 1px;
- color: grey;
- transform-origin: top;
- border-top: 0px;
- overflow:hidden;
- transition:height 0.3s ease-out;
- height:0px;
-
- // .filter-keybar {
- // display: flex;
- // flex-wrap: wrap;
- // justify-content: space-evenly;
- // height: auto;
- // width: 100%;
- // flex-direction: row-reverse;
- // margin-top:5px;
-
- // .filter-item {
- // position: relative;
- // border:1px solid grey;
- // border-radius: 16px;
- // }
- // }
- }
-}
-
-// .top-filter-header {
-
-// #header {
-// text-transform: uppercase;
-// letter-spacing: 2px;
-// font-size: 13;
-// width: 80%;
-// }
-
-// .close-icon {
-// width: 20%;
-// opacity: .6;
-// position: relative;
-// display: block;
-
-// .line {
-// display: block;
-// background: $alt-accent;
-// width: 20;
-// height: 3;
-// position: absolute;
-// right: 0;
-// border-radius: ($height-line / 2);
-
-// &.line-1 {
-// transform: rotate(45deg);
-// top: 45%;
-// }
-
-// &.line-2 {
-// transform: rotate(-45deg);
-// top: 45%;
-// }
-// }
-// }
-
-// .close-icon:hover {
-// opacity: 1;
-// }
-
-// }
-
-// .filter-options {
-
-// .filter-div {
-// margin-top: 10px;
-// margin-bottom: 10px;
-// display: inline-block;
-// width: 100%;
-// border-color: rgba(178, 206, 248, .2); // $darker-alt-accent
-// border-top-style: solid;
-
-// .filter-header {
-// display: flex;
-// align-items: center;
-// margin-bottom: 10px;
-// letter-spacing: 2px;
-
-// .filter-title {
-// font-size: 13;
-// text-transform: uppercase;
-// margin-top: 10px;
-// margin-bottom: 10px;
-// -webkit-transition: all 0.2s ease-in-out;
-// -moz-transition: all 0.2s ease-in-out;
-// -o-transition: all 0.2s ease-in-out;
-// transition: all 0.2s ease-in-out;
-// }
-// }
-
-// .filter-header:hover .filter-title {
-// transform: scale(1.05);
-// }
-
-// .filter-panel {
-// max-height: 0px;
-// width: 100%;
-// overflow: hidden;
-// opacity: 0;
-// transform-origin: top;
-// -webkit-transition: all 0.2s ease-in-out;
-// -moz-transition: all 0.2s ease-in-out;
-// -o-transition: all 0.2s ease-in-out;
-// transition: all 0.2s ease-in-out;
-// text-align: center;
-// }
-// }
-// }
-
-// .filter-buttons {
-// border-color: rgba(178, 206, 248, .2); // $darker-alt-accent
-// border-top-style: solid;
-// padding-top: 10px;
-// }
-
-
-.active-filters {
- display: flex;
- flex-direction: row-reverse;
- justify-content: flex-end;
- width: 100%;
- margin-right: 30px;
- position: relative;
-
- .active-icon {
- max-width: 40px;
- flex: initial;
-
- &.icon {
- width: 40px;
- text-align: center;
- margin-bottom: 5px;
- position: absolute;
- }
-
- &.container {
- display: flex;
- flex-direction: column;
- width: 40px;
- }
-
- &.description {
- text-align: center;
- top: 40px;
- position: absolute;
- width: 40px;
- font-size: 9px;
- opacity: 0;
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
- }
-
- &.icon:hover+.description {
- opacity: 1;
- }
- }
-
- .col-icon {
- height: 35px;
- margin-left: 5px;
- width: 35px;
- background-color: black;
- color: white;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .save-filter,
- .reset-filter,
- .all-filter {
- background-color: gray;
- }
-
- .save-filter:hover,
- .reset-filter:hover,
- .all-filter:hover {
- background-color: $darker-alt-accent;
- }
- }
} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 770a03cb1..b37ae02c3 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,183 +1,99 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, runInAction, reaction, IReactionDisposer } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import * as rp from 'request-promise';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from '../../../fields/FieldSymbols';
+import { Copy, Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { returnFalse, Utils } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { SetupDrag } from '../../util/DragManager';
import { SearchUtil } from '../../util/SearchUtil';
-import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
-import { CollectionView, CollectionViewType } from '../collections/CollectionView';
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionSchemaView, ColumnType } from "../collections/CollectionSchemaView";
+import { CollectionViewType } from '../collections/CollectionView';
import { ViewBoxBaseComponent } from "../DocComponent";
-import { DocumentView } from '../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./SearchBox.scss";
+import { undoBatch } from "../../util/UndoManager";
-export const searchSchema = createSchema({
- id: "string",
- Document: Doc,
- searchQuery: "string",
-});
-
-export enum Keys {
- TITLE = "title",
- AUTHOR = "author",
- DATA = "data",
- TEXT = "text"
-}
+export const searchSchema = createSchema({ Document: Doc });
type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
-//React.Component<SearchProps>
@observer
export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
+ public static Instance: SearchBox;
- get _searchString() { return this.layoutDoc.searchQuery; }
- @computed set _searchString(value) { this.layoutDoc.searchQuery = (value); }
- @observable private _resultsOpen: boolean = false;
- @observable _searchbarOpen: boolean = false;
- @observable private _results: [Doc, string[], string[]][] = [];
- @observable private _openNoResults: boolean = false;
- @observable private _visibleElements: JSX.Element[] = [];
- @observable private _visibleDocuments: Doc[] = [];
-
- static NUM_SEARCH_RESULTS_PER_PAGE = 25;
-
- private _resultsSet = new Map<Doc, number>();
- private _resultsRef = React.createRef<HTMLDivElement>();
- public inputRef = React.createRef<HTMLInputElement>();
-
- private _isSearch: ("search" | "placeholder" | undefined)[] = [];
- private _isSorted: ("sorted" | "placeholder" | undefined)[] = [];
-
+ private _allIcons: string[] = [DocumentType.INK, DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
+ private _numResultsPerPage = 500;
private _numTotalResults = -1;
private _endIndex = -1;
-
- static Instance: SearchBox;
-
+ private _lockPromise?: Promise<void>;
+ private _resultsSet = new Map<Doc, number>();
+ private _inputRef = React.createRef<HTMLInputElement>();
private _maxSearchIndex: number = 0;
private _curRequest?: Promise<any> = undefined;
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.DOCHOLDER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
- private new_buckets: { [characterName: string]: number } = {};
- //if true, any keywords can be used. if false, all keywords are required.
- //this also serves as an indicator if the word status filter is applied
- @observable private _basicWordStatus: boolean = false;
- @observable private _nodeStatus: boolean = false;
- @observable private _keyStatus: boolean = false;
+ private docsforfilter: Doc[] | undefined = [];
+ private realTotalResults: number = 0;
+ private newsearchstring = "";
+ private collectionRef = React.createRef<HTMLDivElement>();
- @observable private newAssign: boolean = true;
+ @observable _icons: string[] = this._allIcons;
+ @observable _results: [Doc, string[], string[]][] = [];
+ @observable _visibleElements: JSX.Element[] = [];
+ @observable _visibleDocuments: Doc[] = [];
+ @observable _deletedDocsStatus: boolean = false;
+ @observable _onlyAliases: boolean = true;
+ @observable _searchbarOpen = false;
+ @observable _searchFullDB = "DB";
+ @observable _noResults = "";
+ @observable _pageStart = 0;
+ @observable open = false;
+ @observable children = 0;
+ @computed get filter() { return this._results?.length && (this.currentSelectedCollection?.props.Document._searchFilterDocs || this.currentSelectedCollection?.props.Document._docFilters); }
constructor(props: any) {
super(props);
SearchBox.Instance = this;
- this.resultsScrolled = this.resultsScrolled.bind(this);
-
}
- @observable setupButtons = false;
- private _disposers: { [name: string]: IReactionDisposer } = {};
-
- componentDidMount = () => {
- this._disposers.filters = reaction(() => Cast(this.props.Document._docFilters, listSpec("string")), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
- newFilters => {
- if (this.searchFullDB) {
- runInAction(() => this._pageStart = 0);
- this.submitSearch();
- // newFilters?.forEach(f => {
- // console.log(f);
- // })
- }
- });
- if (this.setupButtons === false) {
- runInAction(() => this.setupButtons = true);
- }
- if (this.inputRef.current) {
- this.inputRef.current.focus();
- runInAction(() => { this._searchbarOpen = true; });
- }
- if (this.rootDoc.searchQuery && this.newAssign) {
- const sq = this.rootDoc.searchQuery;
- runInAction(() => {
-
- // this._deletedDocsStatus=this.props.filterQuery!.deletedDocsStatus;
- // this._authorFieldStatus=this.props.filterQuery!.authorFieldStatus
- // this._titleFieldStatus=this.props.filterQuery!.titleFieldStatus;
- // this._basicWordStatus=this.props.filterQuery!.basicWordStatus;
- // this._icons=this.props.filterQuery!.icons;
- this.newAssign = false;
- });
- runInAction(() => {
- this.layoutDoc._searchString = StrCast(sq);
- this.submitSearch();
- });
+ componentDidMount = action(() => {
+ if (this._inputRef.current) {
+ this._inputRef.current.focus();
}
- }
+ this._disposers.filters = reaction(() => this.props.Document._docFilters,
+ (filters: any) => this.setSearchFilter(this.currentSelectedCollection, !this.filter ? undefined : this.docsforfilter));
+ });
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
}
+ @computed get currentSelectedCollection() { return CollectionDockingView.Instance; }
- @action
- getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc)
-
-
- @observable newsearchstring: string = "";
- @action.bound
- onChange(e: React.ChangeEvent<HTMLInputElement>) {
- this.layoutDoc._searchString = e.target.value;
+ onChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
this.newsearchstring = e.target.value;
if (e.target.value === "") {
- if (this.currentSelectedCollection !== undefined) {
- let newarray: Doc[] = [];
- let docs: Doc[] = [];
- docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>();
- d._docFilters = new List();
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
-
- this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]);
- this.currentSelectedCollection.props.Document._docFilters = new List();
- this.props.Document.selectedDoc = undefined;
- }
- this._results.forEach(result => {
- Doc.UnBrushDoc(result[0]);
- result[0].searchMatch = undefined;
- });
+ console.log("Reset start");
+ this.docsforfilter = undefined;
+ this.setSearchFilter(this.currentSelectedCollection, undefined);
+ this.resetSearch(false);
- if (this.currentSelectedCollection !== undefined) {
- this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]);
- this.currentSelectedCollection = undefined;
- this.props.Document.selectedDoc = undefined;
-
- }
- runInAction(() => { this.open = false; });
- this._openNoResults = false;
+ this.open = false;
this._results = [];
this._resultsSet.clear();
this._visibleElements = [];
@@ -186,61 +102,16 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
this._curRequest = undefined;
this._maxSearchIndex = 0;
}
- }
+ });
- enter = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
+ enter = action((e: React.KeyboardEvent | undefined) => {
+ if (!e || e.key === "Enter") {
this.layoutDoc._searchString = this.newsearchstring;
- runInAction(() => this._pageStart = 0);
-
- if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) {
- runInAction(() => this.open = true);
- }
- else {
- runInAction(() => this.open = false);
-
- }
+ this._pageStart = 0;
+ this.open = StrCast(this.layoutDoc._searchString) !== "" || this._searchFullDB !== "DB";
this.submitSearch();
}
- }
-
- @observable open: boolean = false;
-
-
- public static async convertDataUri(imageUri: string, returnedFilename: string) {
- try {
- const posting = Utils.prepend("/uploadURI");
- const returnedUri = await rp.post(posting, {
- body: {
- uri: imageUri,
- name: returnedFilename
- },
- json: true,
- });
- return returnedUri;
-
- } catch (e) {
- console.log("SearchBox:" + e);
- }
- }
-
- public _allIcons: string[] = [DocumentType.INK, DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
- //if true, any keywords can be used. if false, all keywords are required.
- //this also serves as an indicator if the word status filter is applied
- @observable private _filterOpen: boolean = false;
- //if icons = all icons, then no icon filter is applied
- // get _icons() { return this.props.searchFileTypes; }
- // set _icons(value) {
- // this.props.setSearchFileTypes(value);
- // }
- @observable _icons: string[] = this._allIcons;
- //if all of these are true, no key filter is applied
- @observable private _titleFieldStatus: boolean = true;
- @observable private _authorFieldStatus: boolean = true;
- //this also serves as an indicator if the collection status filter is applied
- @observable public _deletedDocsStatus: boolean = false;
- @observable private _collectionStatus = false;
-
+ });
getFinalQuery(query: string): string {
//alters the query so it looks in the correct fields
@@ -248,8 +119,6 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
//TODO: data
const initialfilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
- const type: string[] = [];
-
const filters: string[] = [];
for (let i = 0; i < initialfilters.length; i = i + 3) {
@@ -277,9 +146,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
const mod = "_t:";
const newWords: string[] = [];
const oldWords = values[0].split(" ");
- oldWords.forEach((word, i) => {
- i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\"");
- });
+ oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""));
query = `(${query}) AND (${newWords.join(" ")})`;
}
else {
@@ -287,194 +154,74 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
const mod = "_t:";
const newWords: string[] = [];
const oldWords = values[i].split(" ");
- oldWords.forEach((word, i) => {
- i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\"");
- });
+ oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""));
const v = "(" + newWords.join(" ") + ")";
if (i === 0) {
- query = `(${query}) AND (${v}`;
- if (values.length === 1) {
- query = query + ")";
- }
- }
- else if (i === values.length - 1) {
- query = query + " OR " + v + ")";
- }
- else {
- query = query + " OR " + v;
+ query = `(${query}) AND (${v}` + (values.length === 1 ? ")" : "");
}
+ else query = query + " OR " + v + (i === values.length - 1 ? ")" : "");
}
}
-
}
-
- // let limit = typepos.length
- // typepos.forEach(i => {
- // if (i === 0) {
- // if (i + 1 === limit) {
- // query = query + " && " + filters[i] + "_t:" + filters;
- // }
- // else if (filters[i] === filters[i + 3]) {
- // query = query + " && (" + filters[i] + "_t:" + filters;
- // }
- // else {
- // query = query + " && " + filters[i] + "_t:" + filters;
- // }
-
- // }
- // else if (i + 3 > filters.length) {
-
- // }
- // else {
-
- // }
-
- // });
-
- // query = this.applyBasicFieldFilters(query);
-
-
-
- query = query.replace(/-\s+/g, '');
- query = query.replace(/-/g, "");
- return query;
- }
-
- basicRequireWords(query: string): string {
- return query.split(" ").join(" + ").replace(/ + /, "");
+ return query.replace(/-\s+/g, '');
}
@action
filterDocsByType(docs: Doc[]) {
const finalDocs: Doc[] = [];
- const blockedTypes: string[] = [DocumentType.PRESELEMENT, DocumentType.DOCHOLDER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
docs.forEach(doc => {
- const layoutresult = Cast(doc.type, "string");
- if (layoutresult && !blockedTypes.includes(layoutresult)) {
- if (layoutresult && this._icons.includes(layoutresult)) {
- finalDocs.push(doc);
- }
+ const layoutresult = StrCast(doc.type, "string") as DocumentType;
+ if (layoutresult && !this._blockedTypes.includes(layoutresult) && this._icons.includes(layoutresult)) {
+ finalDocs.push(doc);
}
});
return finalDocs;
}
- addCollectionFilter(query: string): string {
- const collections: Doc[] = this.getCurCollections();
- const oldWords = query.split(" ");
-
- const collectionString: string[] = [];
- collections.forEach(doc => {
- const proto = doc.proto;
- const protoId = (proto || doc)[Id];
- const colString: string = "{!join from=data_l to=id}id:" + protoId + " ";
- collectionString.push(colString);
- });
-
- let finalColString = collectionString.join(" ");
- finalColString = finalColString.trim();
- return "+(" + finalColString + ")" + query;
- }
-
- get filterTypes() {
- return this._icons.length === this._allIcons.length ? undefined : this._icons;
- }
-
- //TODO: basically all of this
- //gets all of the collections of all the docviews that are selected
- //if a collection is the only thing selected, search only in that collection (not its container)
- getCurCollections(): Doc[] {
- const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments();
- const collections: Doc[] = [];
- selectedDocs.forEach(async element => {
- const layout: string = StrCast(element.props.Document.layout);
- //checks if selected view (element) is a collection. if it is, adds to list to search through
- if (layout.indexOf("Collection") > -1) {
- //makes sure collections aren't added more than once
- if (!collections.includes(element.props.Document)) {
- collections.push(element.props.Document);
- }
- }
- //makes sure collections aren't added more than once
- if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) {
- collections.push(element.props.ContainingCollectionDoc);
- }
- });
-
- return collections;
+ static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) {
+ let newarray: Doc[] = [];
+ while (docs.length > 0) {
+ newarray = [];
+ docs.forEach(d => {
+ const fieldKey = Doc.LayoutFieldKey(d);
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
+ const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+ data && newarray.push(...DocListCast(data));
+ func(d);
+ });
+ docs = newarray;
+ }
}
-
- currentSelectedCollection: DocumentView | undefined = undefined;
- docsforfilter: Doc[] = [];
-
+ @action
searchCollection(query: string) {
- const selectedCollection: DocumentView = SelectionManager.SelectedDocuments()[0];
+ const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0];
query = query.toLowerCase();
if (selectedCollection !== undefined) {
- this.currentSelectedCollection = selectedCollection;
- if (this.filter === true) {
- this.props.Document.selectedDoc = selectedCollection.props.Document;
- }
- let docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
+ // this._currentSelectedCollection = selectedCollection;
+ const docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
const found: [Doc, string[], string[]][] = [];
- const docsforFilter: Doc[] = [];
- let newarray: Doc[] = [];
+ SearchBox.foreachRecursiveDoc(docs, (doc: Doc) => {
+ const hlights = new Set<string>();
+ SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
+ Array.from(hlights.keys()).length > 0 && found.push([doc, Array.from(hlights.keys()), []]);
+ });
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- d ? console.log(Cast(d.context, Doc)) : null;
- if (d.data !== undefined) {
- newarray.push(...DocListCast(d.data));
- }
- const hlights: string[] = [];
- const protos = Doc.GetAllPrototypes(d);
- protos.forEach(proto => {
- Object.keys(proto).forEach(key => {
- if (StrCast(d[key]).toLowerCase().includes(query) && !hlights.includes(key)) {
- hlights.push(key);
- }
- });
- });
- if (hlights.length > 0) {
- found.push([d, hlights, []]);
- docsforFilter.push(d);
- }
- });
- docs = newarray;
- }
this._results = found;
- this.docsforfilter = docsforFilter;
- if (this.filter === true) {
- selectedCollection.props.Document._searchDocs = new List<Doc>(docsforFilter);
- docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>(docsforFilter);
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
- }
+ this.docsforfilter = this._results.map(r => r[0]);
+ this.setSearchFilter(selectedCollection, this.filter && found.length ? this.docsforfilter : undefined);
this._numTotalResults = found.length;
+ this.realTotalResults = found.length;
}
else {
- this.noresults = "No collection selected :(";
+ this._noResults = "No collection selected :(";
}
}
-
- documentKeys(doc: Doc) {
+ static documentKeys(doc: Doc) {
const keys: { [key: string]: boolean } = {};
// bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
// then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
@@ -482,151 +229,72 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
// then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
// is displayed (unlikely) it won't show up until something else changes.
//TODO Types
- Doc.GetAllPrototypes(doc).map
- (proto => Object.keys(proto).forEach(key => keys[key] = false));
+ Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false));
return Array.from(Object.keys(keys));
}
- applyBasicFieldFilters(query: string) {
- let finalQuery = "";
-
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE);
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
-
- if (this._deletedDocsStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT);
- }
- return finalQuery;
- }
-
- basicFieldFilters(query: string, type: string): string {
- let mod = "";
- switch (type) {
- case Keys.AUTHOR: mod = " author_t:"; break;
- case Keys.TITLE: mod = " title_t:"; break;
- case Keys.TEXT: mod = " text_t:"; break;
- }
-
- const newWords: string[] = [];
- const oldWords = query.split(" ");
- oldWords.forEach(word => newWords.push(mod + word));
-
- query = newWords.join(" ");
-
- return query;
- }
-
- get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
-
@action
- submitSearch = async (reset?: boolean) => {
- if (this.currentSelectedCollection !== undefined) {
- let newarray: Doc[] = [];
- let docs: Doc[] = [];
- docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>();
- //d._docFilters = new List();
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
+ submitSearch = async () => {
+ this.resetSearch(false);
- this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]);
- this.currentSelectedCollection.props.Document._docFilters = new List();
- this.props.Document.selectedDoc = undefined;
- }
- if (reset) {
- this.layoutDoc._searchString = "";
- }
//this.props.Document._docFilters = new List();
- this.noresults = "";
+ this._noResults = "";
this.dataDoc[this.fieldKey] = new List<Doc>([]);
- this.headercount = 0;
this.children = 0;
- this.buckets = [];
- this.new_buckets = {};
let query = StrCast(this.layoutDoc._searchString);
Doc.SetSearchQuery(query);
- this.searchFullDB ? query = this.getFinalQuery(query) : console.log("local");
- this._results.forEach(result => {
- Doc.UnBrushDoc(result[0]);
- result[0].searchMatch = undefined;
- });
+ this._searchFullDB && (query = this.getFinalQuery(query));
this._results = [];
this._resultsSet.clear();
- this._isSearch = [];
- this._isSorted = [];
this._visibleElements = [];
this._visibleDocuments = [];
- if (StrCast(this.props.Document.searchQuery)) {
- if (this._timeout) { clearTimeout(this._timeout); this._timeout = undefined; }
- this._timeout = setTimeout(() => {
- console.log("Resubmitting search");
- }, 60000);
- }
- if (query !== "") {
+ if (query || this._searchFullDB === "My Stuff") {
this._endIndex = 12;
this._maxSearchIndex = 0;
this._numTotalResults = -1;
- this.searchFullDB ? await this.getResults(query) : this.searchCollection(query);
+ this._searchFullDB ? await this.searchDatabase(query) : this.searchCollection(query);
runInAction(() => {
- this._resultsOpen = true;
- this._searchbarOpen = true;
- this._openNoResults = true;
+ this.open = this._searchbarOpen = true;
this.resultsScrolled();
-
});
}
}
- @observable searchFullDB = true;
-
- @observable _timeout: any = undefined;
-
- @observable firststring: string = "";
- @observable secondstring: string = "";
-
- @observable bucketcount: number[] = [];
- @observable buckets: Doc[] | undefined;
-
getAllResults = async (query: string) => {
return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 });
}
private get filterQuery() {
- const types = ["preselement", "docholder", "search", "searchitem", "script", "fonticonbox", "button", "label"]; // this.filterTypes;
- const baseExpr = "NOT baseProto_b:true AND NOT system_b:true";
- const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true";
- const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox";
- const typeExpr = !types ? "" : ` ${types.map(type => `NOT ({!join from=id to=proto_i}type_t:${type}) AND NOT type_t:${type}`).join(" AND ")}`;
+ const baseExpr = "NOT system_b:true";
+ const authorExpr = this._searchFullDB === "My Stuff" ? ` author_t:${Doc.CurrentUserEmail}` : undefined;
+ const includeDeleted = this._deletedDocsStatus ? "" : " NOT deleted_b:true";
+ const typeExpr = this._onlyAliases ? "NOT {!join from=id to=proto_i}type_t:*" : `(type_t:* OR {!join from=id to=proto_i}type_t:*) ${this._blockedTypes.map(type => `NOT ({!join from=id to=proto_i}type_t:${type}) AND NOT type_t:${type}`).join(" AND ")}`;
// fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello
- const query = [baseExpr, includeDeleted, includeIcons, typeExpr].join(" AND ").replace(/AND $/, "");
- return query;
+ return [baseExpr, authorExpr, includeDeleted, typeExpr].filter(q => q).join(" AND ").replace(/AND $/, "");
}
- getDataStatus() { return this._deletedDocsStatus; }
+ @computed get primarySort() {
+ const suffixMap = (type: ColumnType) => {
+ switch (type) {
+ case ColumnType.Date: return "_d";
+ case ColumnType.String: return "_t";
+ case ColumnType.Boolean: return "_b";
+ case ColumnType.Number: return "_n";
+ }
+ };
+ const headers = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
+ return headers.reduce((p: Opt<string>, header: SchemaHeaderField) => p || (header.desc !== undefined && suffixMap(header.type) ? (header.heading + suffixMap(header.type) + (header.desc ? " desc" : " asc")) : undefined), undefined);
+ }
- private NumResults = 50;
- private lockPromise?: Promise<void>;
- getResults = async (query: string) => {
- if (this.lockPromise) {
- await this.lockPromise;
- }
- this.lockPromise = new Promise(async res => {
+ searchDatabase = async (query: string) => {
+ this._lockPromise && (await this._lockPromise);
+ this._lockPromise = new Promise(async res => {
while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) {
- this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => {
+ this._curRequest = SearchUtil.Search(query, true, { onlyAliases: true, allowAliases: true, /*sort: this.primarySort,*/ fq: this.filterQuery, start: 0, rows: this._numResultsPerPage, hl: "on", "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => {
// happens at the beginning
- this.realTotalResults = res.numFound;
+ this.realTotalResults = res.numFound <= 0 ? 0 : res.numFound;
if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
this._numTotalResults = res.numFound;
}
@@ -634,55 +302,49 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
const highlightList = res.docs.map(doc => highlighting[doc[Id]]);
const lines = new Map<string, string[]>();
res.docs.map((doc, i) => lines.set(doc[Id], res.lines[i]));
- const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
+ const docs = res.docs;
const highlights: typeof res.highlighting = {};
docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]);
const filteredDocs = this.filterDocsByType(docs);
- runInAction(() => {
- filteredDocs.forEach((doc, i) => {
- const index = this._resultsSet.get(doc);
- const highlight = highlights[doc[Id]];
- const line = lines.get(doc[Id]) || [];
- const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)).filter(k => k) : [];
- // if (this.findCommonElements(hlights)) {
- // }
- if (index === undefined) {
- this._resultsSet.set(doc, this._results.length);
- this._results.push([doc, hlights, line]);
- } else {
- this._results[index][1].push(...hlights);
- this._results[index][2].push(...line);
- }
+ runInAction(() => filteredDocs.forEach((doc, i) => {
+ const index = this._resultsSet.get(doc);
+ const highlight = highlights[doc[Id]];
+ const line = lines.get(doc[Id]) || [];
+ const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)).filter(k => k) : [];
+ // if (this.findCommonElements(hlights)) {
+ // }
+ if (index === undefined) {
+ this._resultsSet.set(doc, this._results.length);
+ this._results.push([doc, hlights, line]);
+ } else {
+ this._results[index][1].push(...hlights);
+ this._results[index][2].push(...line);
+ }
- });
- });
+ }));
this._curRequest = undefined;
}));
- this._maxSearchIndex += this.NumResults;
+ this._maxSearchIndex += this._numResultsPerPage;
await this._curRequest;
}
this.resultsScrolled();
+
+ const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0];
+ this.docsforfilter = this._results.map(r => r[0]);
+ this.setSearchFilter(selectedCollection, this.filter ? this.docsforfilter : undefined);
res();
});
- return this.lockPromise;
+ return this._lockPromise;
}
- @observable noresults = "";
- collectionRef = React.createRef<HTMLSpanElement>();
+
startDragCollection = async () => {
const res = await this.getAllResults(this.getFinalQuery(StrCast(this.layoutDoc._searchString)));
const filtered = this.filterDocsByType(res.docs);
- const docs = filtered.map(doc => {
- const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
- if (isProto) {
- return Doc.MakeDelegate(doc);
- } else {
- return Doc.MakeAlias(doc);
- }
- });
+ const docs = filtered.map(doc => Doc.GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeDelegate(doc) : Doc.MakeAlias(doc));
let x = 0;
let y = 0;
for (const doc of docs.map(d => Doc.Layout(d))) {
@@ -706,28 +368,26 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
y += 300;
}
}
- return Docs.Create.SchemaDocument(Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []), DocListCast(this.dataDoc[this.fieldKey]), { _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString) });
+ const headers = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).map(h => { const v = h[Copy](); v.color = "#f1efeb"; return v; });
+ return Docs.Create.SchemaDocument(headers, DocListCast(this.dataDoc[this.fieldKey]), { _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString) });
}
@action.bound
openSearch(e: React.SyntheticEvent) {
e.stopPropagation();
- this._openNoResults = false;
- this._resultsOpen = true;
- this._searchbarOpen = true;
+ this._results.forEach(result => Doc.BrushDoc(result[0]));
}
- realTotalResults: number = 0;
-
- @action.bound
- closeSearch = () => {
- //this.closeResults();
- this._searchbarOpen = false;
- }
+ resetSearch = action((close: boolean) => {
+ this._results.forEach(result => {
+ Doc.UnBrushDoc(result[0]);
+ Doc.ClearSearchMatches();
+ });
+ close && (this.open = this._searchbarOpen = false);
+ });
@action.bound
closeResults() {
- this._resultsOpen = false;
this._results = [];
this._resultsSet.clear();
this._visibleElements = [];
@@ -737,21 +397,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
this._curRequest = undefined;
}
- @observable _pageStart: number = 0;
- @observable _pageCount: number = SearchBox.NUM_SEARCH_RESULTS_PER_PAGE;
-
- @observable children: number = 0;
@action
resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
- if (!this._resultsRef.current) return;
this._endIndex = 30;
const headers = new Set<string>(["title", "author", "text", "type", "data", "*lastModified", "context"]);
- // if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) {
- // if (this.noresults === "") {
- // this.noresults = "No search results :(";
- // }
- // return;
- // }
if (this._numTotalResults <= this._maxSearchIndex) {
this._numTotalResults = this._results.length;
@@ -763,301 +412,170 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
// undefined until a searchitem is put in there
this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
this._visibleDocuments = Array<Doc>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
- // indicates if things are placeholders
- this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
- this._isSorted = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
-
}
- let max = this.NumResults;
+ let max = this._numResultsPerPage;
max > this._results.length ? max = this._results.length : console.log("");
for (let i = this._pageStart; i < max; i++) {
//if the index is out of the window then put a placeholder in
//should ones that have already been found get set to placeholders?
- if (this._isSearch[i] !== "search") {
- let result: [Doc, string[], string[]] | undefined = undefined;
-
- result = this._results[i];
- if (result) {
- const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
- const lines = new List<string>(result[2]);
- highlights.forEach((item) => headers.add(item));
- result[0].lines = lines;
- result[0].highlighting = highlights.join(", ");
- result[0].searchMatch = true;
- if (i < this._visibleDocuments.length) {
- this._visibleDocuments[i] = result[0];
- this._isSearch[i] = "search";
- Doc.BrushDoc(result[0]);
- Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]);
- this.children++;
- }
-
+ let result: [Doc, string[], string[]] | undefined = undefined;
+
+ result = this._results[i];
+ if (result) {
+ const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
+ const lines = new List<string>(result[2]);
+ highlights.forEach((item) => headers.add(item));
+ Doc.SetSearchMatch(result[0], { searchMatch: 1 });
+ if (i < this._visibleDocuments.length) {
+ this._visibleDocuments[i] = result[0];
+ Doc.BrushDoc(result[0]);
+ Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]);
+ this.children++;
}
-
}
}
- this.headerscale = headers.size;
- if (Cast(this.props.Document._docFilters, listSpec("string"), []).length === 0) {
- const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
- if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
- const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
- headers.forEach(header => {
- if (oldSchemaHeaders.includes(header) === false) {
- newSchemaHeaders.push(new SchemaHeaderField(header, "#f1efeb"));
- }
- });
- this.headercount = newSchemaHeaders.length;
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
- } else if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
- }
+ if (this.props.Document._schemaHeaders === undefined) {
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
}
if (this._maxSearchIndex >= this._numTotalResults) {
this._visibleElements.length = this._results.length;
this._visibleDocuments.length = this._results.length;
- this._isSearch.length = this._results.length;
}
}
- @observable headercount: number = 0;
- @observable headerscale: number = 0;
-
- findCommonElements(arr2: string[]) {
- const arr1 = ["layout", "data"];
- return arr1.some(item => arr2.includes(item));
- }
-
- @computed
- get resFull() { return this._numTotalResults <= 8; }
-
- @computed
- get resultHeight() { return this._numTotalResults * 70; }
-
- addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
- remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
- moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
- @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); }
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
+ panelHeight = () => this.props.PanelHeight();
+ selectElement = (doc: Doc) => { /* this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); */ };
+ returnHeight = () => NumCast(this.layoutDoc._height);
+ returnLength = () => Math.min(window.innerWidth, 51 + 205 * Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length);
- @computed get viewspec() { return Cast(this.props.Document._docFilters, listSpec("string"), []); }
-
- getTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
- }
- panelHeight = () => {
- return this.props.PanelHeight();
- }
- selectElement = (doc: Doc) => {
- //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex));
- }
-
- addDocument = (doc: Doc) => {
- return null;
+ @action
+ changeSearchScope = (scope: string) => {
+ this.docsforfilter = undefined;
+ this.setSearchFilter(this.currentSelectedCollection, undefined);
+ this._searchFullDB = scope;
+ this.dataDoc[this.fieldKey] = new List<Doc>([]);
+ this.submitSearch();
+ }
+
+ @computed get scopeButtons() {
+ return <div style={{ height: 25, paddingLeft: "4px", paddingRight: "4px", border: "1px solid gray", borderRadius: "0.3em", borderBottom: !this.open ? "1px solid" : "none", }}>
+ <form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}>
+ <div style={{ display: "contents" }}>
+ <div className="radio" style={{ margin: 0 }}>
+ <label style={{ fontSize: 12, marginTop: 6 }} >
+ <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={!this._searchFullDB} onChange={() => this.changeSearchScope("")} />
+ Dashboard
+ </label>
+ </div>
+ <div className="radio" style={{ margin: 0 }}>
+ <label style={{ fontSize: 12, marginTop: 6 }} >
+ <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={this._searchFullDB?.length ? true : false} onChange={() => this.changeSearchScope("DB")} />
+ DB
+ <span onClick={action(() => this._searchFullDB = this._searchFullDB === "My Stuff" ? "DB" : "My Stuff")}>
+ {this._searchFullDB === "My Stuff" ? "(me)" : "(full)"}
+ </span>
+ </label>
+ </div>
+ </div>
+ </form>
+ </div>;
}
- @observable filter = false;
+ setSearchFilter = action((collectionView: { props: { Document: Doc } }, docsForFilter: Doc[] | undefined) => {
+ if (collectionView) {
+ const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), null);
+ collectionView.props.Document._searchFilterDocs = docsForFilter?.length ? new List<Doc>(docsForFilter) : undefined;
+ collectionView.props.Document._docFilters = docsForFilter?.length && docFilters?.length ? new List<string>(docFilters) : undefined;
+ }
+ });
- @action newpage() {
- this._pageStart += SearchBox.NUM_SEARCH_RESULTS_PER_PAGE;
- this.dataDoc[this.fieldKey] = new List<Doc>([]);
- this.resultsScrolled();
- }
render() {
- this.props.Document._chromeStatus === "disabled";
- this.props.Document._searchDoc = true;
- const cols = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length;
- let length = 0;
- length = cols * 205 + 51;
- let height = 0;
- const rows = this.children;
- height = 31 + 31 * 6;
+ const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
return (
<div style={{ pointerEvents: "all" }} className="searchBox-container">
<div className="searchBox-bar">
- <div style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div>
- <div style={{ display: "flex", alignItems: "center" }}>
- <Tooltip title={<div className="dash-tooltip" >drag search results as collection</div>} ><div>
- <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg"
- style={{ cursor: "hand", color: "black", padding: 1, left: 35, position: "relative" }} />
- </div></Tooltip>
- <div style={{
- position: "relative",
- left: 245,
- zIndex: 9000,
- color: "grey",
- background: "white",
- }}> {`${this._results.length}` + " of " + `${this.realTotalResults}`}</div>
- <div style={{ cursor: "default", left: 250, position: "relative", }}>
- <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} ><div>
- <FontAwesomeIcon icon={"filter"} size="lg"
- style={{ cursor: "hand", padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }}
- onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined); }}
- onClick={action(() => {
- ///DONT Change without emailing andy r first.
- this.filter = !this.filter && !this.searchFullDB;
- if (this.filter === true && this.currentSelectedCollection !== undefined) {
- this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter);
- let newarray: Doc[] = [];
- let docs: Doc[] = [];
- docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>(this.docsforfilter);
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
-
- this.currentSelectedCollection.props.Document._docFilters = new List<string>(this.viewspec);
- this.props.Document.selectedDoc = this.currentSelectedCollection.props.Document;
- }
- else if (this.filter === false && this.currentSelectedCollection !== undefined) {
- let newarray: Doc[] = [];
- let docs: Doc[] = [];
- docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>();
- d._docFilters = new List();
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
-
- this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]);
- this.currentSelectedCollection.props.Document._docFilters = new List();
- this.props.Document.selectedDoc = undefined;
- }
- }
- )} />
- </div></Tooltip></div>
- <input value={this.newsearchstring} autoComplete="off" onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef}
- className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch}
- style={{ padding: 1, paddingLeft: 20, paddingRight: 20, color: "black", height: 20, width: 250 }} />
- <div style={{
- height: 25,
- paddingLeft: "4px",
- paddingRight: "4px",
- border: "1px solid gray",
- borderRadius: "0.3em",
- borderBottom: this.open === false ? "1px solid" : "none",
- }}>
- <form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}>
- <div style={{ display: "contents" }}>
- <div className="radio" style={{ margin: 0 }}>
- <label style={{ fontSize: 12, marginTop: 6 }} >
- <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={!this.searchFullDB} onChange={() => {
- runInAction(() => {
- this.searchFullDB = !this.searchFullDB;
- this.dataDoc[this.fieldKey] = new List<Doc>([]);
- if (this.currentSelectedCollection !== undefined) {
- let newarray: Doc[] = [];
- let docs: Doc[] = [];
- docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>();
- d._docFilters = new List();
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
- this.currentSelectedCollection.props.Document._docFilters = new List();
- this.currentSelectedCollection.props.Document._searchDocs = undefined;
- this.currentSelectedCollection = undefined;
- }
- this.submitSearch();
- });
- }} />
- Collection
- </label>
- </div>
- <div className="radio" style={{ margin: 0 }}>
- <label style={{ fontSize: 12, marginTop: 6 }} >
- <input style={{ marginLeft: -16, marginTop: -1 }} type="radio" checked={this.searchFullDB} onChange={() => {
- runInAction(() => {
- this.searchFullDB = !this.searchFullDB;
- this.dataDoc[this.fieldKey] = new List<Doc>([]);
- this.filter = false;
- if (this.currentSelectedCollection !== undefined) {
- let newarray: Doc[] = [];
- let docs: Doc[] = [];
- docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]);
- while (docs.length > 0) {
- newarray = [];
- docs.forEach((d) => {
- if (d.data !== undefined) {
- d._searchDocs = new List<Doc>();
- d._docFilters = new List();
- const newdocs = DocListCast(d.data);
- newdocs.forEach((newdoc) => {
- newarray.push(newdoc);
- });
- }
- });
- docs = newarray;
- }
- this.currentSelectedCollection.props.Document._docFilters = new List();
- this.currentSelectedCollection.props.Document._searchDocs = undefined;
- this.currentSelectedCollection = undefined;
- }
- this.submitSearch();
- });
- }} />
- DB
- </label>
- </div>
- </div>
- </form>
+ <div className="searchBox-lozenges" >
+ <div className="searchBox-lozenge-user">
+ {`${Doc.CurrentUserEmail}`}
+ <div className="searchBox-logoff" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
+ Logoff
+ </div>
+ </div>
+ <div className="searchBox-lozenge">
+ {`UI project`}
+ </div>
+ <div className="searchBox-lozenge-dashboard" >
+ <select className="searchBox-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
+ value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}>
+ {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
+ </select>
+ <div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}>
+ New
+ </div>
+ <div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.snapshotDashboard(Doc.UserDoc()))}>
+ Snapshot
+ </div>
</div>
</div>
-
- </div>
- <div style={{ zIndex: 20000, color: "black" }}>
- {this._searchbarOpen === true ?
+ <div className="searchBox-query" >
+ <input defaultValue={""} autoComplete="off" onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this._inputRef}
+ className="searchBox-barChild searchBox-input" onKeyPress={this.enter}
+ style={{ padding: 1, paddingLeft: 20, paddingRight: 60, color: "black", height: 20, width: 250 }} />
+ <div style={{ display: "flex", alignItems: "center" }}>
+ <div style={{ position: "absolute", left: 10 }}>
+ <Tooltip title={<div className="dash-tooltip" >drag search results as collection</div>}>
+ <div ref={this.collectionRef}><FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg"
+ style={{ cursor: "hand", color: "black", padding: 1, position: "relative" }} /></div>
+ </Tooltip>
+ </div>
+ <div style={{ position: "absolute", left: 200, width: 30, zIndex: 9000, color: "grey", background: "white", }}>
+ {`${this._results.length}` + " of " + `${this.realTotalResults}`}
+ </div>
+ <div style={{ cursor: "default", left: 235, position: "absolute", }}>
+ <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} >
+ <div>
+ <FontAwesomeIcon icon={"filter"} size="lg"
+ style={{ cursor: "hand", padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }}
+ onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => this.layoutDoc._searchString ? this.startDragCollection() : undefined); }}
+ onClick={action(() => this.setSearchFilter(this.currentSelectedCollection, this.filter ? undefined : this.docsforfilter))} />
+ </div>
+ </Tooltip>
+ </div>
+ {this.scopeButtons}
+ </div>
+ </div >
+ </div >
+ {!this._searchbarOpen ? (null) :
+ <div style={{ zIndex: 20000, color: "black" }} ref={(r) => r?.focus()}>
<div style={{ display: "flex", justifyContent: "center", }}>
- {this.noresults === "" ? <div style={{ display: this.open === true ? "flex" : "none", overflow: "auto", }}>
- <CollectionView {...this.props}
+ <div style={{ display: this.open ? "flex" : "none", overflow: "auto", position: "absolute" }}>
+ <CollectionSchemaView {...this.props}
+ CollectionView={undefined}
+ annotationsKey={""}
+ addDocument={returnFalse}
Document={this.props.Document}
moveDocument={returnFalse}
removeDocument={returnFalse}
- PanelHeight={this.open === true ? () => height : () => 0}
- PanelWidth={this.open === true ? () => length : () => 0}
- overflow={length > window.innerWidth || rows > 6 ? true : false}
+ PanelHeight={this.open ? this.returnHeight : returnZero}
+ PanelWidth={this.open ? this.returnLength : returnZero}
+ overflow={length > window.innerWidth || this.children > 6 ? true : false}
focus={this.selectElement}
ScreenToLocalTransform={Transform.Identity}
/>
- </div> :
- <div style={{ display: "flex", justifyContent: "center" }}><div style={{ height: 200, top: 54, minWidth: 400, position: "absolute", backgroundColor: "rgb(241, 239, 235)", display: "flex", justifyContent: "center", alignItems: "center", border: "black 1px solid", }}>
- <div>{this.noresults}</div>
- </div></div>}
- </div> : undefined}
- </div>
-
- <div className="searchBox-results" onScroll={this.resultsScrolled} style={{
- display: this._resultsOpen ? "flex" : "none",
- height: this.resFull ? "auto" : this.resultHeight,
- overflow: "visibile" // this.resFull ? "auto" : "visible"
- }} ref={this._resultsRef}>
- </div>
+ <div style={{ position: "absolute", right: 5, bottom: 7, width: 15, height: 15, }}
+ onPointerDown={e => setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+ this.props.Document._height = NumCast(this.props.Document._height) + delta[1];
+ return false;
+ }, returnFalse, emptyFunction)}
+ >
+ <FontAwesomeIcon icon="grip-lines" size="lg" />
+ </div>
+ </div>
+ </div>
+ </div>
+ }
</div >
);
}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 647e1ce6f..82c0e19c8 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -1,20 +1,16 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
-import { FieldViewProps, FieldView } from "../nodes/FieldView";
-import { observable, action } from "mobx";
-import { DocumentDecorations } from "../DocumentDecorations";
-import "../../views/nodes/WebBox.scss";
-import "./DashWebRTCVideo.scss";
-import { initialize, hangup, refreshVideos } from "./WebCamLogic";
+import { faPhoneSlash, faSync } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faSync, faPhoneSlash } from "@fortawesome/free-solid-svg-icons";
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
import { Doc } from "../../../fields/Doc";
import { InkTool } from "../../../fields/InkField";
-
-library.add(faSync);
-library.add(faPhoneSlash);
+import "../../views/nodes/WebBox.scss";
+import { DocumentDecorations } from "../DocumentDecorations";
+import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import "./DashWebRTCVideo.scss";
+import { hangup, initialize, refreshVideos } from "./WebCamLogic";
+import React = require("react");
/**
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index bee62663e..48106d978 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -20,14 +20,14 @@ export class DateField extends ObjectField {
}
toString() {
- return `${this.date.toISOString()}`;
+ return `${this.date.toLocaleString()}`;
}
[ToScriptString]() {
return `new DateField(new Date(${this.date.toISOString()}))`;
}
[ToString]() {
- return this.date.toISOString();
+ return this.date.toLocaleString();
}
getDate() {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ea53bc9a2..08d949b5e 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -23,6 +23,7 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u
import { LinkManager } from "../client/util/LinkManager";
import JSZip = require("jszip");
import { saveAs } from "file-saver";
+import { CollectionDockingView } from "../client/views/collections/CollectionDockingView";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -91,6 +92,9 @@ export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
export function DocListCast(field: FieldResult): Doc[] {
return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
}
+export function DocListCastOrNull(field: FieldResult) {
+ return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined;
+}
export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
@@ -195,6 +199,8 @@ export class Doc extends RefField {
@observable
private ___fieldKeys: any = {};
+ @observable
+ public [AclSym]: { [key: string]: symbol };
private [UpdatingFromServer]: boolean = false;
@@ -204,19 +210,11 @@ export class Doc extends RefField {
private [Self] = this;
private [SelfProxy]: any;
- public [FieldsSym] = (clear?: boolean) => {
- if (clear) {
- this.___fields = {};
- this.___fieldKeys = {};
- }
- return this.___fields;
- }
- @observable
- public [AclSym]: { [key: string]: symbol };
+ public [FieldsSym](clear?: boolean) { return clear ? this.___fields = this.___fieldKeys = {} : this.___fields; }
public [WidthSym] = () => NumCast(this[SelfProxy]._width);
public [HeightSym] = () => NumCast(this[SelfProxy]._height);
- public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
- public [ToString]() { return `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
+ public [ToScriptString] = () => `DOC-"${this[Self][Id]}"-`;
+ public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`;
public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
public get [DataSym]() {
const self = this[SelfProxy];
@@ -346,6 +344,9 @@ export namespace Doc {
export function IsBaseProto(doc: Doc) {
return GetT(doc, "baseProto", "boolean", true);
}
+ export function IsSystem(doc: Doc) {
+ return GetT(doc, "system", "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;
@@ -500,7 +501,7 @@ export namespace Doc {
}
export function MakeAlias(doc: Doc, id?: string) {
- const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
+ const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
const layout = Doc.LayoutField(alias);
if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) {
Doc.SetLayout(alias, Doc.MakeAlias(layout));
@@ -781,6 +782,7 @@ export namespace Doc {
if (doc) {
const delegate = new Doc(id, true);
delegate.proto = doc;
+ delegate.author = Doc.CurrentUserEmail;
title && (delegate.title = title);
return delegate;
}
@@ -790,7 +792,9 @@ export namespace Doc {
let _applyCount: number = 0;
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
- const target = Doc.MakeDelegate(new Doc());
+ const proto = new Doc();
+ proto.author = Doc.CurrentUserEmail;
+ const target = Doc.MakeDelegate(proto);
const targetKey = StrCast(templateDoc.layoutKey, "layout");
const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
target.layoutKey = targetKey;
@@ -881,6 +885,7 @@ export namespace Doc {
export class DocBrush {
BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
+ SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap();
}
const brushManager = new DocBrush();
@@ -906,6 +911,34 @@ export namespace Doc {
export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; }
export function GetSelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; }
export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
+
+ export function IsSearchMatch(doc: Doc) {
+ return computedFn(function IsSearchMatch(doc: Doc) {
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+ brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ })(doc);
+ }
+ export function IsSearchMatchUnmemoized(doc: Doc) {
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+ brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ }
+ export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
+ if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+ brushManager.SearchMatchDoc.set(doc, results);
+ }
+ return doc;
+ }
+ export function SearchMatchNext(doc: Doc, backward: boolean) {
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+ const result = brushManager.SearchMatchDoc.get(doc);
+ const num = Math.abs(result?.searchMatch || 0) + 1;
+ runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
+ return doc;
+ }
+ export function ClearSearchMatches() {
+ brushManager.SearchMatchDoc.clear();
+ }
+
export function IsBrushed(doc: Doc) {
return computedFn(function IsBrushed(doc: Doc) {
return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
@@ -999,10 +1032,10 @@ export namespace Doc {
const fieldVal = doc[key];
if (Cast(fieldVal, listSpec("string"), []).length) {
const vals = Cast(fieldVal, listSpec("string"), []);
- return vals.some(v => v === value);
+ return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
const fieldStr = Field.toString(fieldVal as Field);
- return fieldStr === value;
+ return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
export function deiconifyView(doc: any) {
@@ -1016,7 +1049,8 @@ export namespace Doc {
doc.layoutKey = deiconify || "layout";
}
export function setDocFilterRange(target: Doc, key: string, range?: number[]) {
- const docRangeFilters = Cast(target._docRangeFilters, listSpec("string"), []);
+ const container = target ?? CollectionDockingView.Instance.props.Document;
+ const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
for (let i = 0; i < docRangeFilters.length; i += 3) {
if (docRangeFilters[i] === key) {
docRangeFilters.splice(i, 3);
@@ -1027,22 +1061,19 @@ export namespace Doc {
docRangeFilters.push(key);
docRangeFilters.push(range[0].toString());
docRangeFilters.push(range[1].toString());
- target._docRangeFilters = new List<string>(docRangeFilters);
+ container._docRangeFilters = new List<string>(docRangeFilters);
}
}
- export function aliasDocs(field: any) {
- return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d)));
- }
-
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Doc, key: string, value: any, modifiers?: "match" | "check" | "x" | undefined) {
+ export function setDocFilter(target: Opt<Doc>, key: string, value: any, modifiers?: "remove" | "match" | "check" | "x" | undefined) {
+ const container = target ?? CollectionDockingView.Instance.props.Document;
const docFilters = Cast(container._docFilters, listSpec("string"), []);
runInAction(() => {
for (let i = 0; i < docFilters.length; i += 3) {
- if (docFilters[i] === key && (docFilters[i + 1] === value || modifiers === "match")) {
+ if (docFilters[i] === key && (docFilters[i + 1] === value || modifiers === "match" || modifiers === "remove")) {
if (docFilters[i + 2] === modifiers && modifiers && docFilters[i + 1] === value) return;
docFilters.splice(i, 3);
container._docFilters = new List<string>(docFilters);
@@ -1052,7 +1083,7 @@ export namespace Doc {
if (typeof modifiers === "string") {
if (!docFilters.length && modifiers === "match" && value === undefined) {
container._docFilters = undefined;
- } else {
+ } else if (modifiers !== "remove") {
docFilters.push(key);
docFilters.push(value);
docFilters.push(modifiers);
@@ -1100,6 +1131,15 @@ export namespace Doc {
return false;
}
+ export function copyDragFactory(dragFactory: Doc) {
+ const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+ if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+ dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+ Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
+ }
+ return ndoc;
+ }
+
export namespace Get {
@@ -1248,8 +1288,8 @@ Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
+Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
-Scripting.addGlobal(function aliasDocs(field: any) { return Doc.aliasDocs(field); });
Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
diff --git a/src/fields/List.ts b/src/fields/List.ts
index a9da75abb..c9e4bd3c1 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,14 +1,16 @@
-import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/SerializationHelper";
+import { action, observable, runInAction } from "mobx";
+import { alias, list, serializable } from "serializr";
+import { DocServer } from "../client/DocServer";
+import { Scripting } from "../client/util/Scripting";
+import { afterDocDeserialize, autoObject, Deserializable } from "../client/util/SerializationHelper";
import { Field } from "./Doc";
-import { setter, getter, deleteProperty, updateFunction } from "./util";
-import { serializable, alias, list } from "serializr";
-import { observable, action, runInAction } from "mobx";
+import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
import { ObjectField } from "./ObjectField";
-import { RefField } from "./RefField";
import { ProxyField } from "./Proxy";
-import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy, Id } from "./FieldSymbols";
-import { Scripting } from "../client/util/Scripting";
-import { DocServer } from "../client/DocServer";
+import { RefField } from "./RefField";
+import { listSpec } from "./Schema";
+import { Cast } from "./Types";
+import { deleteProperty, getter, setter, updateFunction } from "./util";
const listHandlers: any = {
/// Mutator methods
@@ -322,10 +324,15 @@ class ListImpl<T extends Field> extends ObjectField {
return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
}
[ToString]() {
- return "List";
+ return `List(${(this as any).length})`;
}
}
export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
-Scripting.addGlobal("List", List); \ No newline at end of file
+Scripting.addGlobal("List", List);
+Scripting.addGlobal(function compareLists(l1: any, l2: any) {
+ const L1 = Cast(l1, listSpec("string"), []);
+ const L2 = Cast(l2, listSpec("string"), []);
+ return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
+}, "compare two lists"); \ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 2ca5ac082..ae5f301d0 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -28,7 +28,7 @@ export class RichTextField extends ObjectField {
}
[ToScriptString]() {
- return `new RichTextField("${this.Data}", "${this.Text}")`;
+ return `new RichTextField("${this.Data.replace(/"/g, "\\\"")}", "${this.Text}")`;
}
[ToString]() {
return this.Text;
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a590c88c4..0b5f14d74 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -278,7 +278,7 @@ export namespace RichTextUtils {
} else {
docid = backingDocId;
}
- return schema.node("image", { src, agnostic, width, docid, float: null, location: "onRight" });
+ return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" });
};
const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 9391f56ac..47efccc99 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -38,14 +38,14 @@ const scriptSchema = createSimpleSchema({
});
async function deserializeScript(script: ScriptField) {
- if (script.script.originalScript === 'getCopy(this.dragFactory, true)') {
- return (script as any).script = (ScriptField.GetCopyOfDragFactory ?? (ScriptField.GetCopyOfDragFactory = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')))?.script;
+ if (script.script.originalScript === 'copyDragFactory(this.dragFactory)') {
+ return (script as any).script = (ScriptField.GetCopyOfDragFactory ?? (ScriptField.GetCopyOfDragFactory = ScriptField.MakeFunction('copyDragFactory(this.dragFactory)')))?.script;
}
if (script.script.originalScript === 'links(self)') {
return (script as any).script = (ScriptField.LinksSelf ?? (ScriptField.LinksSelf = ComputedField.MakeFunction('links(self)')))?.script;
}
- if (script.script.originalScript === 'openOnRight(getCopy(this.dragFactory, true))') {
- return (script as any).script = (ScriptField.OpenOnRight ?? (ScriptField.OpenOnRight = ComputedField.MakeFunction('openOnRight(getCopy(this.dragFactory, true))')))?.script;
+ if (script.script.originalScript === 'openOnRight(copyDragFactory(this.dragFactory))') {
+ return (script as any).script = (ScriptField.OpenOnRight ?? (ScriptField.OpenOnRight = ComputedField.MakeFunction('openOnRight(copyDragFactory(this.dragFactory))')))?.script;
}
if (script.script.originalScript === 'deiconifyView(self)') {
return (script as any).script = (ScriptField.DeiconifyView ?? (ScriptField.DeiconifyView = ComputedField.MakeFunction('deiconifyView(self)')))?.script;
@@ -73,6 +73,13 @@ async function deserializeScript(script: ScriptField) {
throw new Error("Couldn't compile loaded script");
}
(script as any).script = comp;
+ if (script.setterscript) {
+ const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options);
+ if (!compset.compiled) {
+ throw new Error("Couldn't compile setter script");
+ }
+ (script as any).setterscript = compset;
+ }
}
@scriptingGlobal
@@ -98,6 +105,7 @@ export class ScriptField extends ObjectField {
if (script?.options.capturedVariables) {
const doc = Doc.assign(new Doc, script.options.capturedVariables);
+ doc.system = true;
this.captures = new ProxyField(doc);
}
this.setterscript = setterscript;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 848a648e1..71294c59c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -13,10 +13,11 @@ export const documentSchema = createSchema({
links: listSpec(Doc), // computed (readonly) list of links associated with this document
// "Location" properties in a very general sense
- currentFrame: "number", // current frame of a frame based collection (e.g., a progressive slide)
+ _curPage: "number", // current page of a page based document
+ _currentFrame: "number", // current frame of a frame based collection (e.g., a progressive slide)
lastFrame: "number", // last frame of a frame based collection (e.g., a progressive slide)
activeFrame: "number", // the active frame of a frame based animated document
- currentTimecode: "number", // current play back time of a temporal document (video / audio)
+ _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
isLabel: "boolean", // whether the document is a label or not (video / audio)
@@ -83,7 +84,10 @@ export const documentSchema = createSchema({
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
+ treeViewLockExpandedView: "boolean", // whether the expanded view can be changed
+ treeViewDefaultExpandedView: "string", // name of field whose contents are displayed by default
treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ treeViewOutlineMode: "boolean", // whether tree view is an outline and clicks edit document titles immediately since double-click opening is turned off
// interaction and linking properties
ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events)
@@ -91,13 +95,13 @@ export const documentSchema = createSchema({
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.
- followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, )
+ followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, )
hideLinkButton: "boolean", // whether the blue link counter button should be hidden
hideAllLinks: "boolean", // whether all individual blue anchor dots should be hidden
linkDisplay: "boolean", // whether a link connection should be shown between link anchor endpoints.
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)
+ _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 a freeformview can pan/zoom
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 3d832636f..82525f92b 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -5,7 +5,7 @@ import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action, trace } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate } from "./FieldSymbols";
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate, ToString, ToScriptString } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
import { ComputedField } from "./ScriptField";
import { ScriptCast, StrCast } from "./Types";
@@ -154,14 +154,19 @@ export enum SharingPermissions {
/**
* Calculates the effective access right to a document for the current user.
*/
-export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
+export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol {
if (!target) return AclPrivate;
+
+ // all changes received fromt the server must be processed as Admin
if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
+ // if the current user is the author of the document / the current user is a member of the admin group
+ const userChecked = user || Doc.CurrentUserEmail;
+ if (userChecked === (target.__fields?.author || target.author)) return AclAdmin;
+
if (target[AclSym] && Object.keys(target[AclSym]).length) {
- // if the current user is the author of the document / the current user is a member of the admin group
- if (Doc.CurrentUserEmail === (target.__fields?.author || target.author) || currentUserGroups.includes("admin")) return AclAdmin;
+ if (currentUserGroups.includes("Admin")) return AclAdmin;
// if the ACL is being overriden or the property being modified is one of the playground fields (which can be freely modified)
if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
@@ -178,7 +183,8 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
for (const [key, value] of Object.entries(target[AclSym])) {
// there are issues with storing fields with . in the name, so they are replaced with _ during creation
// as a result we need to restore them again during this comparison.
- if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
+ const entity = key.substring(4).replace('_', '.'); // an individual or a group
+ if (currentUserGroups.includes(entity) || userChecked === entity) {
if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
effectiveAcl = value as symbol;
if (effectiveAcl === AclAdmin) break;
@@ -208,52 +214,53 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
["Admin", 4]
]);
- let changed = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
+ let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
+ let dataDocChanged = false;
const dataDoc = target[DataSym];
// if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive
if (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!) {
target[key] = acl;
- changed = true;
+ layoutDocChanged = true;
+ }
+
+ if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
+ dataDoc[key] = acl;
+ dataDocChanged = true;
// maps over the aliases of the document
- const aliases = DocListCast(target.aliases);
+ const aliases = DocListCast(dataDoc.aliases);
if (aliases.length) {
aliases.map(alias => {
alias !== target && distributeAcls(key, acl, alias, inheritingFromCollection);
});
}
- }
-
- if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
- dataDoc[key] = acl;
- changed = true;
-
// maps over the children of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
- if (d.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
+ if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
distributeAcls(key, acl, d, inheritingFromCollection);
}
const data = d[DataSym];
- if (data && data.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
+ if (data && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
distributeAcls(key, acl, data, inheritingFromCollection);
}
});
// maps over the annotations of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
- if (d.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
+ if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
distributeAcls(key, acl, d, inheritingFromCollection);
}
const data = d[DataSym];
- if (data && data.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
+ if (data && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
distributeAcls(key, acl, data, inheritingFromCollection);
}
});
}
- changed && fetchProto(target); // updates target[AclSym] when changes to acls have been made
+ layoutDocChanged && fetchProto(target); // updates target[AclSym] when changes to acls have been made
+ dataDocChanged && fetchProto(dataDoc);
}
const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
@@ -286,7 +293,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
let prop = in_prop;
- if (in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];
+ if (in_prop === "toString" || in_prop === ToString || in_prop === ToScriptString || in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];
if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
if (prop === LayoutSym) {
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 8ae504f1b..5e86246c5 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -3,18 +3,18 @@ import "./ImageUpload.scss";
import React = require('react');
import { observer } from 'mobx-react';
import { observable, action, computed } from 'mobx';
-import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, returnTrue, returnEmptyFilter } from '../Utils';
+import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, returnTrue, returnEmptyFilter, returnEmptyDoclist } from '../Utils';
import { Doc, Opt } from '../fields/Doc';
import { Cast, FieldValue } from '../fields/Types';
import { listSpec } from '../fields/Schema';
-import MainViewModal from '../client/views/MainViewModal';
+import { MainViewModal } from '../client/views/MainViewModal';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { nullAudio } from '../fields/URLField';
import { Transform } from '../client/util/Transform';
import { DocumentView } from '../client/views/nodes/DocumentView';
import { MobileInterface } from './MobileInterface';
import { DictationOverlay } from '../client/views/DictationOverlay';
-import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
+import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
import { ContextMenu } from '../client/views/ContextMenu';
@observer
@@ -53,7 +53,7 @@ export class AudioUpload extends React.Component {
* Pushing the audio doc onto Dash Web through the right side bar
*/
uploadAudio = () => {
- const audioRightSidebar = Cast(Doc.UserDoc()["sidebar-sharing"], Doc) as Doc;
+ const audioRightSidebar = Cast(Doc.UserDoc().mySharedDocs, Doc) as Doc;
const audioDoc = this._audioCol;
const data = Cast(audioRightSidebar.data, listSpec(Doc));
for (let i = 1; i < 8; i++) {
@@ -88,6 +88,7 @@ export class AudioUpload extends React.Component {
rootSelected={returnTrue}
removeDocument={undefined}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
onClick={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index d21d326f6..1ee4c7815 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,25 +1,27 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
import * as rp from 'request-promise';
-import { Docs } from '../client/documents/Documents';
-import "./ImageUpload.scss";
-import React = require('react');
import { DocServer } from '../client/DocServer';
-import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import { Utils } from '../Utils';
+import { Docs } from '../client/documents/Documents';
import { Networking } from '../client/Network';
+import { DFLT_IMAGE_NATIVE_DIM } from '../client/views/globalCssVariables.scss';
+import { MainViewModal } from '../client/views/MainViewModal';
import { Doc, Opt } from '../fields/Doc';
-import { Cast } from '../fields/Types';
-import { listSpec } from '../fields/Schema';
import { List } from '../fields/List';
-import MainViewModal from '../client/views/MainViewModal';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { listSpec } from '../fields/Schema';
+import { Cast } from '../fields/Types';
+import { Utils } from '../Utils';
+import "./ImageUpload.scss";
import { MobileInterface } from './MobileInterface';
+import React = require('react');
export interface ImageUploadProps {
Document: Doc; // Target document for upload (upload location)
}
const inputRef = React.createRef<HTMLInputElement>();
+const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@observer
export class Uploader extends React.Component<ImageUploadProps> {
@@ -52,13 +54,13 @@ export class Uploader extends React.Component<ImageUploadProps> {
let doc = null;
// Case 1: File is a video
if (file.type === "video/mp4") {
- doc = Docs.Create.VideoDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ doc = Docs.Create.VideoDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name });
// Case 2: File is a PDF document
} else if (file.type === "application/pdf") {
- doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, _fitWidth: true, title: name });
+ doc = Docs.Create.PdfDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, _fitWidth: true, title: name });
// Case 3: File is another document type (most likely Image)
} else {
- doc = Docs.Create.ImageDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ doc = Docs.Create.ImageDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name });
}
this.setOpacity(4, "1"); // Slab 4
const res = await rp.get(Utils.prepend("/getUserDocumentId"));
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 02993fdcb..ae2c07c8e 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,4 +1,4 @@
-import * as React from "react";
+
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
@@ -10,34 +10,35 @@ import {
faAlignRight, faAlignLeft
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, reaction, trace, runInAction } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, DocListCast } from '../fields/Doc';
-import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
+import * as React from "react";
import { Docs, DocumentOptions } from '../client/documents/Documents';
+import { DocumentType } from "../client/documents/DocumentTypes";
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
import { Scripting } from '../client/util/Scripting';
-import { DocumentView } from '../client/views/nodes/DocumentView';
+import { SettingsManager } from '../client/util/SettingsManager';
import { Transform } from '../client/util/Transform';
-import "./MobileInterface.scss";
-import "./ImageUpload.scss";
-import "./AudioUpload.scss";
-import SettingsManager from '../client/util/SettingsManager';
-import { Uploader } from "./ImageUpload";
-import { DockedFrameRenderer } from '../client/views/collections/CollectionDockingView';
-import { InkTool } from '../fields/InkField';
-import GestureOverlay from "../client/views/GestureOverlay";
-import { ScriptField } from "../fields/ScriptField";
-import { RadialMenu } from "../client/views/nodes/RadialMenu";
import { UndoManager } from "../client/util/UndoManager";
+import { TabDocView } from '../client/views/collections/TabDocView';
+import { CollectionViewType } from "../client/views/collections/CollectionView";
+import { GestureOverlay } from "../client/views/GestureOverlay";
+import { AudioBox } from "../client/views/nodes/AudioBox";
+import { DocumentView } from '../client/views/nodes/DocumentView';
+import { RichTextMenu } from "../client/views/nodes/formattedText/RichTextMenu";
+import { RadialMenu } from "../client/views/nodes/RadialMenu";
+import { Doc, DocListCast } from '../fields/Doc';
+import { InkTool } from '../fields/InkField';
import { List } from "../fields/List";
-import { AudioUpload } from "./AudioUpload";
+import { ScriptField } from "../fields/ScriptField";
import { Cast, FieldValue } from '../fields/Types';
-import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
-import { AudioBox } from "../client/views/nodes/AudioBox";
-import { CollectionViewType } from "../client/views/collections/CollectionView";
-import { DocumentType } from "../client/documents/DocumentTypes";
-import { CollectionFreeFormViewChrome } from "../client/views/collections/CollectionMenu";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
+import { AudioUpload } from "./AudioUpload";
+import { Uploader } from "./ImageUpload";
+import "./AudioUpload.scss";
+import "./ImageUpload.scss";
+import "./MobileInterface.scss";
+import { InkStrokeProperties } from '../client/views/InkStrokeProperties';
library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -47,10 +48,11 @@ library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngl
faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft,
faAlignLeft, faAlignRight);
+
@observer
export class MobileInterface extends React.Component {
static Instance: MobileInterface;
- private _library: Doc = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web
+ private _library: Promise<Doc>;
private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc());
@observable private _sidebarActive: boolean = false; //to toggle sidebar display
@observable private _imageUploadActive: boolean = false; //to toggle image upload
@@ -58,7 +60,7 @@ export class MobileInterface extends React.Component {
@observable private _menuListView: boolean = false; //to switch between menu view (list / icon)
@observable private _ink: boolean = false; //toggle whether ink is being dispalyed
@observable private _homeMenu: boolean = true; // to determine whether currently at home menu
- @observable private _child: Doc | null = null; // currently selected document
+ @observable private dashboards: Doc | null = null; // currently selected document
@observable private _activeDoc: Doc = this._mainDoc; // doc updated as the active mobile page is updated (initially home menu)
@observable private _homeDoc: Doc = this._mainDoc; // home menu as a document
@observable private _parents: Array<Doc> = []; // array of parent docs (for pathbar)
@@ -67,6 +69,7 @@ export class MobileInterface extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
+ this._library = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web
MobileInterface.Instance = this;
}
@@ -123,7 +126,7 @@ export class MobileInterface extends React.Component {
* Method called when 'Library' button is pressed on the home screen
*/
switchToLibrary = async () => {
- this.switchCurrentView(this._library);
+ this._library.then(library => this.switchCurrentView(library));
runInAction(() => this._homeMenu = false);
this.toggleSidebar();
}
@@ -135,19 +138,19 @@ export class MobileInterface extends React.Component {
back = () => {
const header = document.getElementById("header") as HTMLElement;
const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document
- // Case 1: Parent document is 'workspaces'
+ // Case 1: Parent document is 'dashboards'
if (doc === Cast(this._library, Doc) as Doc) {
- this._child = null;
- this.switchCurrentView(this._library);
+ this.dashboards = null;
+ this._library.then(library => this.switchCurrentView(library));
// Case 2: Parent document is the 'home' menu (root node)
} else if (doc === Cast(this._homeDoc, Doc) as Doc) {
this._homeMenu = true;
this._parents = [];
- this._child = null;
+ this.dashboards = null;
this.switchCurrentView(this._homeDoc);
// Case 3: Parent document is any document
} else if (doc) {
- this._child = doc;
+ this.dashboards = doc;
this.switchCurrentView(doc);
this._homeMenu = false;
header.textContent = String(doc.title);
@@ -163,7 +166,7 @@ export class MobileInterface extends React.Component {
if (!this._homeMenu || this._sidebarActive) {
this._homeMenu = true;
this._parents = [];
- this._child = null;
+ this.dashboards = null;
this.switchCurrentView(this._homeDoc);
}
if (this._sidebarActive) {
@@ -172,14 +175,14 @@ export class MobileInterface extends React.Component {
}
/**
- * Return to primary Workspace in library (Workspaces Doc)
+ * Return to primary Dashboard in library (Dashboards Doc)
*/
@action
returnMain = () => {
this._parents = [this._homeDoc];
- this.switchCurrentView(this._library);
+ this._library.then(library => this.switchCurrentView(library));
this._homeMenu = false;
- this._child = null;
+ this.dashboards = null;
}
/**
@@ -193,7 +196,7 @@ export class MobileInterface extends React.Component {
/**
* DocumentView for graphic display of all documents
*/
- @computed get displayWorkspaces() {
+ @computed get displayDashboards() {
return !this.mainContainer ? (null) :
<div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
<DocumentView
@@ -219,6 +222,7 @@ export class MobileInterface extends React.Component {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
/>
@@ -242,7 +246,7 @@ export class MobileInterface extends React.Component {
this._parents.push(this._activeDoc);
this.switchCurrentView(doc);
this._homeMenu = false;
- this._child = doc;
+ this.dashboards = doc;
}
});
}
@@ -257,7 +261,7 @@ export class MobileInterface extends React.Component {
this._parents.push(this._activeDoc);
this.switchCurrentView(doc);
this._homeMenu = false;
- this._child = doc;
+ this.dashboards = doc;
this.toggleSidebar();
}
@@ -286,15 +290,16 @@ export class MobileInterface extends React.Component {
// Handles when user clicks on a document in the pathbar
@action
- handlePathClick = (doc: Doc, index: number) => {
- if (doc === this._library) {
- this._child = null;
+ handlePathClick = async (doc: Doc, index: number) => {
+ const library = await this._library;
+ if (doc === library) {
+ this.dashboards = null;
this.switchCurrentView(doc);
this._parents.length = index;
} else if (doc === this._homeDoc) {
this.returnHome();
} else {
- this._child = doc;
+ this.dashboards = doc;
this.switchCurrentView(doc);
this._parents.length = index;
}
@@ -319,13 +324,13 @@ export class MobileInterface extends React.Component {
</div>
);
}
- // stores workspace documents as 'workspaces' variable
- let workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc) as Doc;
- if (this._child) {
- workspaces = this._child;
+ // stores dashboards documents as 'dashboards' variable
+ let dashboards = CurrentUserUtils.MyDashboards;
+ if (this.dashboards) {
+ dashboards = this.dashboards;
}
// returns a list of navbar buttons as 'buttons'
- const buttons = DocListCast(workspaces.data).map((doc: Doc, index: any) => {
+ const buttons = DocListCast(dashboards.data).map((doc: Doc, index: any) => {
if (doc.type !== "ink") {
return (
<div
@@ -355,7 +360,7 @@ export class MobileInterface extends React.Component {
{this.renderPathbar()}
<div className={`sidebar ${this._sidebarActive ? "active" : ""}`}>
<div className="sidebarButtons">
- {this._child ?
+ {this.dashboards ?
<>
{buttons}
<div
@@ -363,7 +368,7 @@ export class MobileInterface extends React.Component {
onClick={this.returnMain}
style={{ opacity: 0.7 }}>
<FontAwesomeIcon className="right" icon="angle-double-left" size="lg" />
- <div className="item-type">Return to workspaces</div>
+ <div className="item-type">Return to dashboards</div>
</div>
</> :
<>
@@ -371,9 +376,9 @@ export class MobileInterface extends React.Component {
<div
className="item"
style={{ opacity: 0.7 }}
- onClick={() => this.createNewWorkspace()}>
+ onClick={() => this.createNewDashboard()}>
<FontAwesomeIcon className="right" icon="plus" size="lg" />
- <div className="item-type">Create New Workspace</div>
+ <div className="item-type">Create New Dashboard</div>
</div>
</>
}
@@ -386,27 +391,27 @@ export class MobileInterface extends React.Component {
}
/**
- * Handles the 'Create New Workspace' button in the menu (taken from MainView.tsx)
+ * Handles the 'Create New Dashboard' button in the menu (taken from MainView.tsx)
*/
@action
- createNewWorkspace = async (id?: string) => {
- const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc) as Doc;
- const workspaceCount = DocListCast(workspaces.data).length + 1;
+ createNewDashboard = async (id?: string) => {
+ const scens = CurrentUserUtils.MyDashboards;
+ const dashboardCount = DocListCast(scens.data).length + 1;
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
- title: "Collection " + workspaceCount,
+ title: "Collection " + dashboardCount,
};
const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+ const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
- const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
- workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
- workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
+ const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
+ dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]);
+ dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Dashboard Layout"]);
- Doc.AddDocToList(workspaces, "data", workspaceDoc);
+ Doc.AddDocToList(scens, "data", dashboardDoc);
}
// Button for switching between pen and ink mode
@@ -436,7 +441,7 @@ export class MobileInterface extends React.Component {
// DocButton that uses UndoManager and handles the opacity change if CanUndo is true
@computed get undo() {
if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
- this._activeDoc !== Doc.UserDoc()["sidebar-sharing"] && this._activeDoc.title !== "WORKSPACES") {
+ this._activeDoc !== Doc.UserDoc().mySharedDocs && this._activeDoc.title !== "WORKSPACES") {
return (
<div className="docButton"
style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
@@ -454,7 +459,7 @@ export class MobileInterface extends React.Component {
// DocButton that uses UndoManager and handles the opacity change if CanRedo is true
@computed get redo() {
if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
- this._activeDoc !== Doc.UserDoc()["sidebar-sharing"] && this._activeDoc.title !== "WORKSPACES") {
+ this._activeDoc !== Doc.UserDoc().mySharedDocs && this._activeDoc.title !== "WORKSPACES") {
return (
<div className="docButton"
style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
@@ -512,7 +517,7 @@ export class MobileInterface extends React.Component {
return <div className="docButton"
title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
- onClick={e => DockedFrameRenderer.PinDoc(this._activeDoc, isPinned)}>
+ onClick={e => TabDocView.PinDoc(this._activeDoc, isPinned)}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
</div>;
} else return (null);
@@ -605,12 +610,12 @@ export class MobileInterface extends React.Component {
// Returns the image upload pop up
@computed get uploadImage() {
- const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.UserDoc()["sidebar-sharing"], Doc) as Doc;
+ const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.UserDoc().mySharedDocs, Doc) as Doc;
return <Uploader Document={doc} />;
}
// Radial menu can only be used if it is a colleciton and it is not a homeDoc
- // (and cannot be used on Workspace to avoid pin to presentation opening on right)
+ // (and cannot be used on Dashboard to avoid pin to presentation opening on right)
@computed get displayRadialMenu() {
return this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
this._activeDoc._viewType !== CollectionViewType.Docking ? <RadialMenu /> : (null);
@@ -628,7 +633,7 @@ export class MobileInterface extends React.Component {
*/
@action
switchToMobileUploads = () => {
- const mobileUpload = Cast(Doc.UserDoc()["sidebar-sharing"], Doc) as Doc;
+ const mobileUpload = Cast(Doc.UserDoc().mySharedDocs, Doc) as Doc;
this.switchCurrentView(mobileUpload);
this._homeMenu = false;
}
@@ -655,7 +660,7 @@ export class MobileInterface extends React.Component {
{this.drawInk}
{this.uploadImageButton}
</div>
- {this.displayWorkspaces}
+ {this.displayDashboards}
{this.renderDefaultContent}
</GestureOverlay>
{this.displayRadialMenu}
@@ -667,7 +672,7 @@ export class MobileInterface extends React.Component {
//Global functions for mobile menu
Scripting.addGlobal(function switchToMobileLibrary() { return MobileInterface.Instance.switchToLibrary(); },
- "opens the library to navigate through workspaces on Dash Mobile");
+ "opens the library to navigate through dashboards on Dash Mobile");
Scripting.addGlobal(function openMobileUploads() { return MobileInterface.Instance.toggleUpload(); },
"opens the upload files menu for Dash Mobile");
Scripting.addGlobal(function switchToMobileUploadCollection() { return MobileInterface.Instance.switchToMobileUploads(); },
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index a5aaab63e..a52430ee8 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -62,7 +62,7 @@ export class SearchManager extends ApiManager {
subscription: "/dashsearch",
secureHandler: async ({ req, res }) => {
const solrQuery: any = {};
- ["q", "fq", "start", "rows", "sort", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]);
+ ["q", "fq", "start", "rows", "sort", "hl.maxAnalyzedChars", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]);
if (solrQuery.q === undefined) {
res.send([]);
return;
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index e088cd2c4..76f5afe16 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -55,7 +55,7 @@ export default class UploadManager extends ApiManager {
const results: Upload.FileResponse[] = [];
for (const key in files) {
const result = await DashUploadUtils.upload(files[key]);
- result && results.push(result);
+ result && !(result.result instanceof Error) && results.push(result);
}
_success(res, results);
resolve();
diff --git a/src/server/Search.ts b/src/server/Search.ts
index decd1f5b1..68f61deb2 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -7,11 +7,10 @@ export namespace Search {
export async function updateDocument(document: any) {
try {
- const res = await rp.post(pathTo("update"), {
+ return await rp.post(pathTo("update"), {
headers: { 'content-type': 'application/json' },
body: JSON.stringify([document])
});
- return res;
} catch (e) {
// console.warn("Search error: " + e + document);
}
@@ -19,11 +18,10 @@ export namespace Search {
export async function updateDocuments(documents: any[]) {
try {
- const res = await rp.post(pathTo("update"), {
+ return await rp.post(pathTo("update"), {
headers: { 'content-type': 'application/json' },
body: JSON.stringify(documents)
});
- return res;
} catch (e) {
// console.warn("Search error: ", e, documents);
}
@@ -31,9 +29,8 @@ export namespace Search {
export async function search(query: any) {
try {
- const searchResults = JSON.parse(await rp.get(pathTo("select"), {
- qs: query
- }));
+ const output = await rp.get(pathTo("select"), { qs: query });
+ const searchResults = JSON.parse(output);
const { docs, numFound } = searchResults.response;
const ids = docs.map((field: any) => field.id);
return { ids, numFound, highlighting: searchResults.highlighting };
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index ec351c283..b33e76c0b 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -290,6 +290,9 @@ export namespace WebSocket {
if (term !== undefined) {
const { suffix, value } = term;
update[key + suffix] = { set: value };
+ if (key.endsWith('lastModified')) {
+ update["lastModified" + suffix] = value;
+ }
}
}
if (dynfield) {