aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLionel Han <47760119+IGoByJoe@users.noreply.github.com>2020-08-08 18:03:31 -0700
committerLionel Han <47760119+IGoByJoe@users.noreply.github.com>2020-08-08 18:03:31 -0700
commit09a7f3711ccc0089b20c1bf9b58679e18b600081 (patch)
treed8c16d95c2ec94c0f8eae24a9869361f8cf9eebb /src
parent1032f8b421402371be4e4d62171bc837fdcb4368 (diff)
parent301b10ba693dc76ebcd42d3fa4020410f2092bee (diff)
Merge branch 'new_audio' of https://github.com/browngraphicslab/Dash-Web into new_audio
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts4
-rw-r--r--src/client/DocServer.ts38
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx2
-rw-r--r--src/client/documents/DocumentTypes.ts4
-rw-r--r--src/client/documents/Documents.ts58
-rw-r--r--src/client/util/CurrentUserUtils.ts254
-rw-r--r--src/client/util/GroupManager.scss1
-rw-r--r--src/client/util/GroupManager.tsx96
-rw-r--r--src/client/util/GroupMemberView.scss5
-rw-r--r--src/client/util/GroupMemberView.tsx11
-rw-r--r--src/client/util/InteractionUtils.tsx46
-rw-r--r--src/client/util/Scripting.ts2
-rw-r--r--src/client/util/SearchUtil.ts2
-rw-r--r--src/client/util/SelectionManager.ts3
-rw-r--r--src/client/util/SettingsManager.scss260
-rw-r--r--src/client/util/SettingsManager.tsx201
-rw-r--r--src/client/util/SharingManager.scss65
-rw-r--r--src/client/util/SharingManager.tsx357
-rw-r--r--src/client/util/type_decls.d5
-rw-r--r--src/client/views/.DS_Storebin6148 -> 10244 bytes
-rw-r--r--src/client/views/ContextMenu.scss5
-rw-r--r--src/client/views/ContextMenuItem.tsx4
-rw-r--r--src/client/views/DocComponent.tsx22
-rw-r--r--src/client/views/DocumentButtonBar.scss8
-rw-r--r--src/client/views/DocumentButtonBar.tsx18
-rw-r--r--src/client/views/DocumentDecorations.scss3
-rw-r--r--src/client/views/DocumentDecorations.tsx135
-rw-r--r--src/client/views/EditableView.tsx45
-rw-r--r--src/client/views/GestureOverlay.tsx130
-rw-r--r--src/client/views/GlobalKeyHandler.ts18
-rw-r--r--src/client/views/InkingStroke.tsx115
-rw-r--r--src/client/views/Main.scss6
-rw-r--r--src/client/views/MainView.scss141
-rw-r--r--src/client/views/MainView.tsx507
-rw-r--r--src/client/views/MainViewModal.tsx6
-rw-r--r--src/client/views/MainViewNotifs.scss2
-rw-r--r--src/client/views/MainViewNotifs.tsx29
-rw-r--r--src/client/views/PreviewCursor.tsx6
-rw-r--r--src/client/views/PropertiesButtons.scss141
-rw-r--r--src/client/views/PropertiesButtons.tsx763
-rw-r--r--src/client/views/RecommendationsBox.scss69
-rw-r--r--src/client/views/RecommendationsBox.tsx201
-rw-r--r--src/client/views/SearchDocBox.tsx428
-rw-r--r--src/client/views/TemplateMenu.tsx9
-rw-r--r--src/client/views/Touchable.tsx2
-rw-r--r--src/client/views/collections/CollectionDockingView.scss106
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx68
-rw-r--r--src/client/views/collections/CollectionLinearView.scss2
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx4
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx2
-rw-r--r--src/client/views/collections/CollectionMenu.scss20
-rw-r--r--src/client/views/collections/CollectionMenu.tsx579
-rw-r--r--src/client/views/collections/CollectionPileView.tsx28
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx132
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx82
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx18
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss34
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx46
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx33
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx19
-rw-r--r--src/client/views/collections/CollectionSubView.tsx99
-rw-r--r--src/client/views/collections/CollectionTreeView.scss15
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx13
-rw-r--r--src/client/views/collections/CollectionView.scss1
-rw-r--r--src/client/views/collections/CollectionView.tsx54
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss15
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx8
-rw-r--r--src/client/views/collections/SchemaTable.tsx96
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx40
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss139
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx77
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss14
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx379
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.scss733
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.tsx1037
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx2
-rw-r--r--src/client/views/globalCssVariables.scss4
-rw-r--r--src/client/views/linking/LinkEditor.scss3
-rw-r--r--src/client/views/linking/LinkEditor.tsx29
-rw-r--r--src/client/views/linking/LinkMenu.scss1
-rw-r--r--src/client/views/linking/LinkMenu.tsx26
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx5
-rw-r--r--src/client/views/nodes/AudioBox.tsx73
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx143
-rw-r--r--src/client/views/nodes/ColorBox.tsx1
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx10
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss8
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx158
-rw-r--r--src/client/views/nodes/DocumentView.scss9
-rw-r--r--src/client/views/nodes/DocumentView.tsx511
-rw-r--r--src/client/views/nodes/FieldView.tsx9
-rw-r--r--src/client/views/nodes/FontIconBox.scss74
-rw-r--r--src/client/views/nodes/FontIconBox.tsx25
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/LabelBox.tsx16
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx5
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx14
-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/PresBox.scss840
-rw-r--r--src/client/views/nodes/PresBox.tsx1735
-rw-r--r--src/client/views/nodes/QueryBox.tsx71
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx2
-rw-r--r--src/client/views/nodes/TaskCompletedBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.scss2
-rw-r--r--src/client/views/nodes/WebBox.tsx70
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx116
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx125
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts7
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts2
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts21
-rw-r--r--src/client/views/pdf/PDFMenu.tsx9
-rw-r--r--src/client/views/pdf/PDFViewer.tsx30
-rw-r--r--src/client/views/presentationview/PresElementBox.scss200
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx176
-rw-r--r--src/client/views/search/FieldFilters.scss12
-rw-r--r--src/client/views/search/FieldFilters.tsx41
-rw-r--r--src/client/views/search/FilterBox.scss178
-rw-r--r--src/client/views/search/FilterBox.tsx431
-rw-r--r--src/client/views/search/SearchBox.scss69
-rw-r--r--src/client/views/search/SearchBox.tsx731
-rw-r--r--src/client/views/search/SearchItem.scss163
-rw-r--r--src/client/views/search/SearchItem.tsx310
-rw-r--r--src/fields/Doc.ts23
-rw-r--r--src/fields/InkField.ts5
-rw-r--r--src/fields/RichTextField.ts2
-rw-r--r--src/fields/ScriptField.ts4
-rw-r--r--src/fields/documentSchemas.ts1
-rw-r--r--src/fields/util.ts95
-rw-r--r--src/mobile/AudioUpload.tsx2
-rw-r--r--src/mobile/MobileInterface.tsx8
-rw-r--r--src/server/ActionUtilities.ts10
-rw-r--r--src/server/ApiManagers/SearchManager.ts2
-rw-r--r--src/server/ApiManagers/UploadManager.ts36
-rw-r--r--src/server/ApiManagers/UtilManager.ts1
-rw-r--r--src/server/DashUploadUtils.ts27
-rw-r--r--src/server/GarbageCollector.ts6
-rw-r--r--src/server/MemoryDatabase.ts4
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/ProcessFactory.ts6
-rw-r--r--src/server/Recommender.ts133
-rw-r--r--src/server/RouteManager.ts8
-rw-r--r--src/server/Search.ts2
-rw-r--r--src/server/database.ts8
-rw-r--r--src/server/downsize.ts2
-rw-r--r--src/server/index.ts34
-rw-r--r--src/server/server_Initialization.ts38
-rw-r--r--src/server/websocket.ts45
-rw-r--r--src/typings/index.d.ts4
152 files changed, 10462 insertions, 4744 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 3f82aa3eb..6608bb176 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,8 +1,8 @@
import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
-import { Socket, Room } from 'socket.io';
-import { Message } from './server/Message';
import { ColorState } from 'react-color';
+import { Socket } from 'socket.io';
+import { Message } from './server/Message';
export namespace Utils {
export let DRAG_THRESHOLD = 4;
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 8ded43468..2fe3e9778 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
import * as io from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, fetchProto, FieldsSym } from '../fields/Doc';
+import { Opt, Doc, fetchProto, FieldsSym, UpdatingFromServer } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../fields/RefField';
@@ -156,23 +156,23 @@ export namespace DocServer {
let _isReadOnly = false;
export function makeReadOnly() {
- if (_isReadOnly) return;
- _isReadOnly = true;
- _CreateField = field => {
- _cache[field[Id]] = field;
- };
- _UpdateField = emptyFunction;
- _RespondToUpdate = emptyFunction;
+ if (!_isReadOnly) {
+ _isReadOnly = true;
+ _CreateField = field => _cache[field[Id]] = field;
+ _UpdateField = emptyFunction;
+ _RespondToUpdate = emptyFunction;
+ }
}
export function makeEditable() {
- if (!_isReadOnly) return;
- location.reload();
- // _isReadOnly = false;
- // _CreateField = _CreateFieldImpl;
- // _UpdateField = _UpdateFieldImpl;
- // _respondToUpdate = _respondToUpdateImpl;
- // _cache = {};
+ if (_isReadOnly) {
+ location.reload();
+ // _isReadOnly = false;
+ // _CreateField = _CreateFieldImpl;
+ // _UpdateField = _UpdateFieldImpl;
+ // _respondToUpdate = _respondToUpdateImpl;
+ // _cache = {};
+ }
}
export function isReadOnly() { return _isReadOnly; }
@@ -228,6 +228,7 @@ export namespace DocServer {
// deserialize
const field = await SerializationHelper.Deserialize(fieldJson);
if (force && field instanceof Doc && cached instanceof Doc) {
+ cached[UpdatingFromServer] = true;
Array.from(Object.keys(field)).forEach(key => {
const fieldval = field[key];
if (fieldval instanceof ObjectField) {
@@ -235,6 +236,8 @@ export namespace DocServer {
}
cached[key] = field[key];
});
+ cached[UpdatingFromServer] = false;
+ return cached;
}
else if (field !== undefined) {
_cache[id] = field;
@@ -369,6 +372,9 @@ export namespace DocServer {
} else if (cached instanceof Promise) {
proms.push(cached as any);
}
+ } else if (field) {
+ proms.push(_cache[field.id] as any);
+ fieldMap[field.id] = field;
}
}
});
@@ -448,7 +454,7 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ (!DocServer.Control.isReadOnly()) && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
let _UpdateField: (id: string, diff: any) => void = errorFunc;
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
index c3e8d2fff..bc95b5f9a 100644
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -18,7 +18,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
@observable private showPasteTargetState = false;
@observable private success: Opt<boolean> = undefined;
@observable private displayLauncher = true;
- @observable private credentials: string;
+ @observable private credentials: string = "";
private disposer: Opt<IReactionDisposer>;
private set isOpen(value: boolean) {
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 7578b7df0..71d6c2ccc 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -13,7 +13,7 @@ export enum DocumentType {
INK = "ink", // ink stroke
SCREENSHOT = "screenshot", // view of a desktop application
FONTICON = "fonticonbox", // font icon
- QUERY = "query", // search query
+ SEARCH = "search", // search query
LABEL = "label", // simple text label
BUTTON = "button", // onClick button
WEBCAM = "webcam", // webcam
@@ -31,11 +31,11 @@ export enum DocumentType {
COLOR = "color", // color picker (view of a color picker for a color string)
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)
GROUP = "group", // group of users
LINKDB = "linkdb", // database of links ??? why do we have this
SCRIPTDB = "scriptdb", // database of scripts
- RECOMMENDATION = "recommendation", // view of a recommendation
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 aa74ef71a..a06b4a581 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -21,6 +21,8 @@ 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 { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
import { ContextMenu } from "../views/ContextMenu";
@@ -31,6 +33,7 @@ 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";
@@ -38,16 +41,13 @@ import { LabelBox } from "../views/nodes/LabelBox";
import { LinkBox } from "../views/nodes/LinkBox";
import { PDFBox } from "../views/nodes/PDFBox";
import { PresBox } from "../views/nodes/PresBox";
-import { QueryBox } from "../views/nodes/QueryBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ScriptingBox } from "../views/nodes/ScriptingBox";
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 { RecommendationsBox } from "../views/RecommendationsBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
-import { DocumentType } from "./DocumentTypes";
import { Networking } from "../Network";
import { Upload } from "../../server/SharedMediaTypes";
const path = require('path');
@@ -93,6 +93,7 @@ export interface DocumentOptions {
type?: string;
title?: string;
label?: string;
+ hidden?: boolean;
toolTip?: string; // tooltip to display on hover
style?: string;
page?: number;
@@ -128,6 +129,7 @@ export interface DocumentOptions {
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
@@ -135,6 +137,12 @@ export interface DocumentOptions {
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;
@@ -152,6 +160,7 @@ export interface DocumentOptions {
annotationOn?: Doc;
removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
dbDoc?: Doc;
+ iconShape?: string; // shapes of the fonticon border
linkRelationship?: string; // type of relatinoship a link represents
ischecked?: ScriptField; // returns whether a font icon box is checked
activeInkPen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
@@ -167,6 +176,7 @@ export interface DocumentOptions {
clipboard?: Doc;
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
@@ -187,14 +197,14 @@ export interface DocumentOptions {
flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse";
selectedIndex?: number;
syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text
- searchText?: string; //for searchbox
- searchQuery?: string; // for queryBox
- filterQuery?: string;
+ searchQuery?: string; // for quersyBox
linearViewIsExpanded?: boolean; // is linear view expanded
isLabel?: boolean; // whether the document is a label or not (video / audio)
useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox
audioStart?: number; // the time frame where the audio should begin playing
audioEnd?: number; // the time frame where the audio should stop playing
+ border?: string; //for searchbox
+ hovercolor?: string;
}
class EmptyBox {
@@ -224,8 +234,8 @@ export namespace Docs {
layout: { view: FormattedTextBox, dataField: "text" },
options: { _height: 150, _xMargin: 10, _yMargin: 10 }
}],
- [DocumentType.QUERY, {
- layout: { view: QueryBox, dataField: defaultDataKey },
+ [DocumentType.SEARCH, {
+ layout: { view: SearchBox, dataField: defaultDataKey },
options: { _width: 400 }
}],
[DocumentType.COLOR, {
@@ -305,10 +315,10 @@ export namespace Docs {
layout: { view: FontIconBox, dataField: defaultDataKey },
options: { _width: 40, _height: 40, borderRounding: "100%" },
}],
- [DocumentType.RECOMMENDATION, {
- layout: { view: RecommendationsBox, dataField: defaultDataKey },
- options: { _width: 200, _height: 200 },
- }],
+ // [DocumentType.RECOMMENDATION, {
+ // layout: { view: RecommendationsBox, dataField: defaultDataKey },
+ // options: { _width: 200, _height: 200 },
+ // }],
[DocumentType.WEBCAM, {
layout: { view: DashWebRTCVideo, dataField: defaultDataKey }
}],
@@ -559,6 +569,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>();
proto.links = ComputedField.MakeFunction("links(self)");
@@ -632,8 +643,8 @@ export namespace Docs {
return instance;
}
- export function QueryDocument(options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.QUERY), "", options);
+ export function SearchDocument(options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List<Doc>([]), options);
}
export function ColorDocument(options: DocumentOptions = {}) {
@@ -726,11 +737,11 @@ export namespace Docs {
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Freeform }, id);
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", ...options, _viewType: CollectionViewType.Pile }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", hideFilterView: true, forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id);
}
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -754,11 +765,11 @@ export namespace Docs {
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Tree }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id);
}
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Stacking }, id);
}
export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
@@ -770,7 +781,7 @@ export namespace Docs {
export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Masonry });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Masonry });
}
export function LabelDocument(options?: DocumentOptions) {
@@ -809,10 +820,6 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.IMPORT), new List<Doc>(), options);
}
- export function RecommendationsDocument(data: Doc[], options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.RECOMMENDATION), new List<Doc>(data), options);
- }
-
export type DocConfig = {
doc: Doc,
initialWidth?: number,
@@ -932,9 +939,6 @@ export namespace DocUtils {
linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2?.title');
- Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
- Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
-
return linkDoc;
}
@@ -1043,6 +1047,7 @@ export namespace DocUtils {
event: (args: { x: number, y: number }) => {
const newDoc = Doc.ApplyTemplate(dragDoc);
if (newDoc) {
+ newDoc.author = Doc.CurrentUserEmail;
newDoc.x = x;
newDoc.y = y;
docAdder(newDoc);
@@ -1062,6 +1067,7 @@ export namespace DocUtils {
}
});
batch.end();
+ return doc;
}
export function findTemplate(templateName: string, type: string, signature: string) {
let docLayoutTemplate: Opt<Doc>;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6d752832a..37ffcb78e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -8,11 +8,11 @@ import { Doc, DocListCast, DocListCastAsync, DataSym } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { ScriptField, ComputedField } from "../../fields/ScriptField";
-import { Cast, PromiseValue, StrCast, NumCast } from "../../fields/Types";
+import { Cast, PromiseValue, StrCast, NumCast, BoolCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { DragManager } from "./DragManager";
import { Scripting } from "./Scripting";
-import { CollectionViewType } from "../views/collections/CollectionView";
+import { CollectionViewType, CollectionView } from "../views/collections/CollectionView";
import { makeTemplate } from "./DropConverter";
import { RichTextField } from "../../fields/RichTextField";
import { PrefetchProxy } from "../../fields/Proxy";
@@ -38,12 +38,14 @@ export class CurrentUserUtils {
@observable public static GuestWorkspace: Doc | undefined;
@observable public static GuestMobile: Doc | undefined;
+ @observable public static propertiesWidth: number = 0;
+
// sets up the default User Templates - slideView, queryView, descriptionView
static setupUserTemplateButtons(doc: Doc) {
if (doc["template-button-query"] === undefined) {
const queryTemplate = Docs.Create.MulticolumnDocument(
[
- Docs.Create.QueryDocument({ title: "query", _height: 200 }),
+ Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, lockedPosition: true, title: "query", _height: 200 }),
Docs.Create.FreeformDocument([], { title: "data", _height: 100 })
],
{ _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true }
@@ -246,6 +248,8 @@ export class CurrentUserUtils {
if (doc["template-buttons"] === undefined) {
doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
+ hidden: ComputedField.MakeFunction("self.target.noviceMode") as any,
+ target: doc,
_autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
}));
@@ -411,16 +415,13 @@ export class CurrentUserUtils {
if (doc.emptyButton === undefined) {
doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button" });
}
- if (doc.emptySearch === undefined) {
- doc.emptySearch = Docs.Create.QueryDocument({ _width: 200, title: "empty search" });
- }
if (doc.emptyDocHolder === undefined) {
doc.emptyDocHolder = Docs.Create.DocumentDocument(
ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any,
{ _width: 250, _height: 250, title: "container" });
}
if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true });
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true });
}
if (doc.activeMobileMenu === undefined) {
this.setupActiveMobileMenu(doc);
@@ -429,7 +430,7 @@ export class CurrentUserUtils {
{ toolTip: "Drag a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
{ toolTip: "Drag a web page", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
{ toolTip: "Drag a cat image", title: "Image", icon: "cat", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyImage as Doc },
- { toolTip: "Drag a comparison box", title: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
+ { toolTip: "Drag a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
{ toolTip: "Drag a screengrabber", 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: "Drag a audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc },
@@ -465,7 +466,7 @@ export class CurrentUserUtils {
}
const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
- _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
+ _nativeWidth: 50, _nativeHeight: 50, _width: 50, _height: 50,
icon,
title,
toolTip,
@@ -492,6 +493,70 @@ export class CurrentUserUtils {
return doc.myItemCreators as Doc;
}
+ static menuBtnDescriptions(): {
+ title: string, icon: string, click: string,
+ }[] {
+ return [
+ { title: "Sharing", icon: "users", click: 'scriptContext.selectMenu(self, "Sharing")' },
+ { title: "Workspace", icon: "desktop", click: 'scriptContext.selectMenu(self, "Workspace")' },
+ { title: "Catalog", icon: "file", click: 'scriptContext.selectMenu(self, "Catalog")' },
+ { title: "Archive", icon: "archive", click: 'scriptContext.selectMenu(self, "Archive")' },
+ { title: "Import", icon: "upload", click: 'scriptContext.selectMenu(self, "Import")' },
+ { title: "Tools", icon: "wrench", click: 'scriptContext.selectMenu(self, "Tools")' },
+ { title: "Help", icon: "question-circle", click: 'scriptContext.selectMenu(self, "Help")' },
+ { title: "Settings", icon: "cog", click: 'scriptContext.selectMenu(self, "Settings")' },
+ { title: "User Doc", icon: "address-card", click: 'scriptContext.selectMenu(self, "UserDoc")' },
+ ];
+ }
+
+ 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,
+ childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack",
+ })) as any as Doc;
+ }
+ }
+ static setupMenuPanel(doc: Doc) {
+ if (doc.menuStack === undefined) {
+ const menuBtns = CurrentUserUtils.menuBtnDescriptions().map(({ title, icon, click }) =>
+ Docs.Create.FontIconDocument({
+ icon,
+ iconShape: "square",
+ title,
+ _backgroundColor: "black",
+ stayInCollection: true,
+ childDropAction: "same",
+ _width: 60,
+ _height: 60,
+ onClick: ScriptField.MakeScript(click, { scriptContext: "any" }),
+ }));
+ const userDoc = menuBtns[menuBtns.length - 1];
+ userDoc.target = doc;
+ userDoc.hidden = ComputedField.MakeFunction("self.target.noviceMode");
+
+ doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
+ title: "menuItemPanel",
+ dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
+ _backgroundColor: "black",
+ _gridGap: 0,
+ _yMargin: 0,
+ _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, lockedPosition: true, _chromeStatus: "disabled",
+ }));
+ }
+ // this resets all sidebar buttons to being deactivated
+ PromiseValue(Cast(doc.menuStack, Doc)).then(stack => {
+ stack && PromiseValue(stack.data).then(btns => {
+ DocListCastAsync(btns).then(bts => bts?.forEach(btn => {
+ btn.color = "white";
+ btn._backgroundColor = "";
+ }));
+ });
+ });
+ return doc.menuStack as Doc;
+ }
+
+
// Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
static setupActiveMobileMenu(doc: Doc) {
if (doc.activeMobileMenu === undefined) {
@@ -590,17 +655,15 @@ export class CurrentUserUtils {
return Cast(userDoc.thumbDoc, Doc);
}
- static setupLibrary(userDoc: Doc) {
- return CurrentUserUtils.setupWorkspaces(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)
- static async setupToolsBtnPanel(doc: Doc, sidebarContainer: Doc) {
+ static async setupToolsBtnPanel(doc: Doc) {
// setup a masonry view of all he creators
const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc);
const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
+ doc["tabs-button-tools"] = undefined;
+
if (doc.myCreators === undefined) {
doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0,
@@ -615,131 +678,115 @@ export class CurrentUserUtils {
doc.myColorPicker = new PrefetchProxy(color);
}
- if (doc["tabs-button-tools"] === undefined) {
+ if (doc["sidebar-tools"] === undefined) {
const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
- _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
+ title: "sidebar-tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", hideFilterView: true, forceActive: true
})) as any as Doc;
- doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 35, _height: 25, title: "Tools", _fontSize: "10pt",
- letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
- sourcePanel: toolsStack,
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
- dragFactory: toolsStack,
- removeDropProperties: new List<string>(["lockedPosition"]),
- stayInCollection: true,
- targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
- onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"),
- }));
+
+ doc["sidebar-tools"] = toolsStack;
}
- (doc["tabs-button-tools"] as Doc).sourcePanel; // prefetch sourcePanel
- return doc["tabs-button-tools"] as Doc;
}
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,
+ title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true,
}));
}
- 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"]);
+ 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"]);
- return doc.myWorkspaces as Doc;
+ const workspaces = doc.myWorkspaces as Doc;
+
+ doc["sidebar-workspaces"] = new PrefetchProxy(Docs.Create.TreeDocument([workspaces], {
+ treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
+ treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true,
+ lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
+ })) 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,
+ childDropAction: "alias", targetDropAction: "same", stayInCollection: true, treeViewOpen: true,
}));
}
- return doc.myCatalog 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",
+ treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
+ treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true,
+ lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
+ })) as any as Doc;
+ }
}
static setupRecentlyClosed(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: true, stayInCollection: true,
+ title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, treeViewOpen: true, stayInCollection: 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));
- 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"]);
+ 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"]);
- return doc.myRecentlyClosed as Doc;
- }
- // setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views
- static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) {
- const workspaces = CurrentUserUtils.setupWorkspaces(doc);
- const documents = CurrentUserUtils.setupCatalog(doc);
- const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc);
-
- if (doc["tabs-button-library"] === undefined) {
- const libraryStack = new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
- title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150,
+ const recentlyClosed = doc.myRecentlyClosed as Doc;
+
+ doc["sidebar-recentlyClosed"] = new PrefetchProxy(Docs.Create.TreeDocument([recentlyClosed], {
+ title: "sidebar-recentlyClosed",
+ treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
+ treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true,
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
})) as any as Doc;
- doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Library", _fontSize: "10pt", targetDropAction: "same",
- letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
- sourcePanel: libraryStack,
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
- dragFactory: libraryStack,
- removeDropProperties: new List<string>(["lockedPosition"]),
- stayInCollection: true,
- targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
- onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel")
- }));
}
- return doc["tabs-button-library"] as Doc;
}
- // setup the Search button which will display the search panel.
- static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) {
- if (doc["tabs-button-search"] === undefined) {
- doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Search", _fontSize: "10pt",
- letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
- sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc,
- searchFileTypes: new List<string>([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]),
- targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
- lockedPosition: true,
- onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel")
- }));
+
+ static setupUserDoc(doc: Doc) {
+ if (doc["sidebar-userDoc"] === 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,
+ lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
+ })) as any as Doc;
}
- return doc["tabs-button-search"] as Doc;
}
static setupSidebarContainer(doc: Doc) {
- if (doc["tabs-panelContainer"] === undefined) {
+ if (doc.sidebar === undefined) {
const sidebarContainer = new Doc();
sidebarContainer._chromeStatus = "disabled";
sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()");
- doc["tabs-panelContainer"] = new PrefetchProxy(sidebarContainer);
+ doc.sidebar = new PrefetchProxy(sidebarContainer);
}
- return doc["tabs-panelContainer"] as Doc;
+ return doc.sidebar as Doc;
}
// setup the list of sidebar mode buttons which determine what is displayed in the sidebar
static async setupSidebarButtons(doc: Doc) {
- const sidebarContainer = CurrentUserUtils.setupSidebarContainer(doc);
- const toolsBtn = await CurrentUserUtils.setupToolsBtnPanel(doc, sidebarContainer);
- const libraryBtn = CurrentUserUtils.setupLibraryPanel(doc, sidebarContainer);
- const searchBtn = CurrentUserUtils.setupSearchBtnPanel(doc, sidebarContainer);
-
- // Finally, setup the list of buttons to display in the sidebar
- if (doc["tabs-buttons"] === undefined) {
- doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([libraryBtn, searchBtn, toolsBtn], {
- _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", _columnsHideIfEmpty: true, ignoreClick: true, _chromeStatus: "view-mode",
- title: "sidebar btn row stack", backgroundColor: "dimGray",
- }));
- (toolsBtn.onClick as ScriptField).script.run({ this: toolsBtn });
- }
+ CurrentUserUtils.setupSidebarContainer(doc);
+ await CurrentUserUtils.setupToolsBtnPanel(doc);
+ CurrentUserUtils.setupWorkspaces(doc);
+ CurrentUserUtils.setupCatalog(doc);
+ CurrentUserUtils.setupRecentlyClosed(doc);
+ CurrentUserUtils.setupUserDoc(doc);
}
static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
@@ -749,7 +796,7 @@ export class CurrentUserUtils {
})) as any as Doc
static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
- ...opts, dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100
+ ...opts, dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40
})) as any as Doc
/// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
@@ -773,6 +820,11 @@ export class CurrentUserUtils {
// the initial presentation Doc to use
static setupDefaultPresentation(doc: Doc) {
+ if (doc["template-presentation"] === undefined) {
+ doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
+ title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data"
+ }));
+ }
if (doc.activePresentation === undefined) {
doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), {
title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias",
@@ -782,9 +834,9 @@ export class CurrentUserUtils {
}
// Right sidebar is where mobile uploads are contained
- static setupRightSidebar(doc: Doc) {
- if (doc.rightSidebarCollection === undefined) {
- doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Mobile Uploads" }));
+ static setupSharingSidebar(doc: Doc) {
+ if (doc["sidebar-sharing"] === undefined) {
+ doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Shared Documents", childDropAction: "alias" }));
}
}
@@ -848,13 +900,19 @@ export class CurrentUserUtils {
doc.activeDash = StrCast(doc.activeDash, "0");
doc.fontSize = StrCast(doc.fontSize, "12pt");
doc.fontFamily = StrCast(doc.fontFamily, "Arial");
+ doc.fontColor = StrCast(doc.fontColor, "black");
+ doc.fontHighlight = StrCast(doc.fontHighlight, "");
+ doc.defaultColor = StrCast(doc.defaultColor, "white");
+ doc.noviceMode = BoolCast(doc.noviceMode, true);
doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
this.setupDocTemplates(doc); // sets up the template menu of templates
- this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing
+ this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing
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
this.setupDefaultPresentation(doc); // presentation that's initially triggered
diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 51e4fa9e2..9438bdd72 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -92,6 +92,7 @@
.sort-groups {
text-align: left;
margin-left: 5;
+ width: 50px;
cursor: pointer;
}
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 72fba5c1b..277e96a89 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -18,8 +18,11 @@ import { setGroups } from "../../fields/util";
import { DocServer } from "../DocServer";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle);
+library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown);
+/**
+ * Interface for options for the react-select component
+ */
export interface UserOptions {
label: string;
value: string;
@@ -30,17 +33,16 @@ export default class GroupManager extends React.Component<{}> {
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
- @observable private dialogueBoxOpacity: number = 1; // opacity of the dialogue box div of the MainViewModal.
- @observable private overlayOpacity: number = 0.4; // opacity of the overlay div of the MainViewModal.
@observable private users: string[] = []; // list of users populated from the database.
@observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
@observable currentGroup: Opt<Doc>; // the currently selected group.
@observable private createGroupModalOpen: boolean = false;
private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
- private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
- private currentUserGroups: string[] = [];
+ private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button
+ private currentUserGroups: string[] = []; // the list of groups the current user is a member of
@observable private buttonColour: "#979797" | "black" = "#979797";
@observable private groupSort: "ascending" | "descending" | "none" = "none";
+ private populating: boolean = false;
@@ -49,6 +51,9 @@ export default class GroupManager extends React.Component<{}> {
GroupManager.Instance = this;
}
+ /**
+ * Populates the list of users and groups.
+ */
componentDidMount() {
this.populateUsers();
this.populateGroups();
@@ -58,33 +63,35 @@ export default class GroupManager extends React.Component<{}> {
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- runInAction(() => this.users = []);
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
- const raw = JSON.parse(userList) as User[];
- const evaluating = raw.map(async user => {
- // const isCandidate = user.email !== Doc.CurrentUserEmail;
- // if (isCandidate) {
- const userDocument = await DocServer.GetRefField(user.userDocumentId);
- if (userDocument instanceof Doc) {
- const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc);
- runInAction(() => {
- if (notificationDoc instanceof Doc) {
- this.users.push(user.email);
- }
- });
- }
- // }
- });
- return Promise.all(evaluating);
+ if (!this.populating) {
+ this.populating = true;
+ runInAction(() => this.users = []);
+ const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const raw = JSON.parse(userList) as User[];
+ 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);
+ runInAction(() => {
+ if (notificationDoc instanceof Doc) {
+ this.users.push(user.email);
+ }
+ });
+ }
+ });
+ return Promise.all(evaluating).then(() => this.populating = false);
+ }
}
+ /**
+ * Populates the list of groups the current user is a member of and sets this list to be used in the GetEffectiveAcl in util.ts
+ */
populateGroups = () => {
DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
groups?.forEach(group => {
const members: string[] = JSON.parse(StrCast(group.members));
if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
});
-
setGroups(this.currentUserGroups);
});
}
@@ -101,7 +108,7 @@ export default class GroupManager extends React.Component<{}> {
*/
@action
open = () => {
- SelectionManager.DeselectAll();
+ // SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
this.populateGroups();
@@ -116,6 +123,7 @@ export default class GroupManager extends React.Component<{}> {
this.currentGroup = undefined;
// this.users = [];
this.createGroupModalOpen = false;
+ TaskCompletionBox.taskCompleted = false;
}
/**
@@ -128,7 +136,6 @@ export default class GroupManager extends React.Component<{}> {
/**
* @returns a list of all group documents.
*/
- // private ?
getAllGroups(): Doc[] {
const groupDoc = this.GroupManagerDoc;
return groupDoc ? DocListCast(groupDoc.data) : [];
@@ -138,32 +145,14 @@ export default class GroupManager extends React.Component<{}> {
* @returns a group document based on the group name.
* @param groupName
*/
- // private?
getGroup(groupName: string): Doc | undefined {
const groupDoc = this.getAllGroups().find(group => group.groupName === groupName);
return groupDoc;
}
/**
- * @returns a readonly copy of a single group document
+ * Returns an array of the list of members of a given group.
*/
- getGroupCopy(groupName: string): Doc | undefined {
- const groupDoc = this.getGroup(groupName);
- if (groupDoc) {
- const { members, owners } = groupDoc;
- return Doc.assign(new Doc, { groupName, members: StrCast(members), owners: StrCast(owners) });
- }
- return undefined;
- }
- /**
- * @returns a readonly copy of the list of group documents
- */
- getAllGroupsCopy(): Doc[] {
- return this.getAllGroups().map(({ groupName, owners, members }) =>
- Doc.assign(new Doc, { groupName: (StrCast(groupName)), owners: (StrCast(owners)), members: (StrCast(members)) })
- );
- }
-
getGroupMembers(group: string | Doc): string[] {
if (group instanceof Doc) return JSON.parse(StrCast(group.members)) as string[];
else return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
@@ -222,8 +211,6 @@ export default class GroupManager extends React.Component<{}> {
deleteGroup(group: Doc): boolean {
if (group) {
if (this.GroupManagerDoc && this.hasEditAccess(group)) {
- // TODO look at this later
- // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
SharingManager.Instance.removeGroup(group);
const members: string[] = JSON.parse(StrCast(group.members));
@@ -316,12 +303,17 @@ export default class GroupManager extends React.Component<{}> {
}
+ /**
+ * @returns the MainViewModal which allows the user to create groups.
+ */
private get groupCreationModal() {
const contents = (
<div className="group-create">
<div className="group-heading" style={{ marginBottom: 0 }}>
<p><b>New Group</b></p>
- <div className={"close-button"} onClick={action(() => this.createGroupModalOpen = false)}>
+ <div className={"close-button"} onClick={action(() => {
+ this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false;
+ })}>
<FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
</div>
</div>
@@ -329,6 +321,7 @@ export default class GroupManager extends React.Component<{}> {
className="group-input"
ref={this.inputRef}
onKeyDown={this.handleKeyDown}
+ autoFocus
type="text"
placeholder="Group name"
onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} />
@@ -373,7 +366,7 @@ export default class GroupManager extends React.Component<{}> {
interactive={true}
contents={contents}
dialogueBoxStyle={{ width: "90%", height: "70%" }}
- closeOnExternalClick={action(() => this.createGroupModalOpen = false)}
+ closeOnExternalClick={action(() => { this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; })}
/>
);
}
@@ -415,7 +408,10 @@ export default class GroupManager extends React.Component<{}> {
<div
className="sort-groups"
onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Name {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
+ 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"} />
+ }
</div>
<div className="group-body">
{groups.map(group =>
diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss
index c609c5c7b..2eb164988 100644
--- a/src/client/util/GroupMemberView.scss
+++ b/src/client/util/GroupMemberView.scss
@@ -41,9 +41,10 @@
margin-top: -5;
height: 20;
text-overflow: ellipsis;
+ background: none;
&:hover {
- text-overflow: visible;
+ text-overflow: unset;
overflow-x: auto;
}
}
@@ -72,7 +73,7 @@
.editing-contents {
overflow-y: auto;
- height: 65%;
+ height: 62%;
width: 100%;
color: black;
margin-top: -15px;
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index f20670c4e..531ef988a 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -29,13 +29,17 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
+ const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group);
+
return (!this.props.group ? null :
<div className="editing-interface">
<div className="editing-header">
<input
className="group-title"
+ style={{ marginLeft: !hasEditAccess ? "-14%" : 0 }}
value={StrCast(this.props.group.groupName)}
onChange={e => this.props.group.groupName = e.currentTarget.value}
+ disabled={!hasEditAccess}
>
</input>
<div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}>
@@ -65,12 +69,15 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
null}
<div
className="sort-emails"
+ style={{ paddingTop: hasEditAccess ? 0 : 35 }}
onClick={action(() => this.memberSort = this.memberSort === "ascending" ? "descending" : this.memberSort === "descending" ? "none" : "ascending")}>
Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */}
</div>
</div>
<hr />
- <div className="editing-contents">
+ <div className="editing-contents"
+ style={{ height: hasEditAccess ? "62%" : "85%" }}
+ >
{members.map(member => (
<div
className="editing-row"
@@ -79,7 +86,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
<div className="user-email">
{member}
</div>
- {GroupManager.Instance.hasEditAccess(this.props.group) ?
+ {hasEditAccess ?
<div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
<FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} />
</div>
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 8b3614ea7..04a750f93 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -99,6 +99,15 @@ export namespace InteractionUtils {
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
}
+ else if (points.length >= 5 && points[3].X === points[4].X) {
+ for (var i = 0; i < points.length - 3; i += 4) {
+ const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
+ for (var t = 0; t < 1; t += 0.01) {
+ const point = beziercurve(t, array);
+ pts.push({ X: point[0], Y: point[1] });
+ }
+ }
+ }
else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
//pointer is up (first and last points are the same)
const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
@@ -118,6 +127,12 @@ export namespace InteractionUtils {
pts.pop();
}
}
+ if (isNaN(scalex)) {
+ scalex = 1;
+ }
+ if (isNaN(scaley)) {
+ scaley = 1;
+ }
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} `, "");
@@ -136,7 +151,6 @@ export namespace InteractionUtils {
<polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
</marker>}
</defs>}
-
<polyline
points={strpts}
style={{
@@ -157,17 +171,6 @@ export namespace InteractionUtils {
</svg>);
}
- // export function makeArrow() {
- // return (
- // InkOptionsMenu.Instance.getColors().map(color => {
- // const id1 = "arrowStartTest" + color;
- // <marker id={id1} orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7">
- // <polygon points="0 0, 3 1, 0 2" fill={"#" + color} />
- // </marker>;
- // })
- // );
- // }
-
export function makePolygon(shape: string, points: { X: number, Y: number }[]) {
if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
//pointer is up (first and last points are the same)
@@ -217,10 +220,28 @@ export namespace InteractionUtils {
points.push({ X: left, Y: top });
return points;
case "triangle":
+ // points.push({ X: left, Y: bottom });
+ // points.push({ X: right, Y: bottom });
+ // points.push({ X: (right + left) / 2, Y: top });
+ // points.push({ X: left, Y: bottom });
+
+ points.push({ X: left, Y: bottom });
points.push({ X: left, Y: bottom });
+
points.push({ X: right, Y: bottom });
+ points.push({ X: right, Y: bottom });
+ points.push({ X: right, Y: bottom });
+ points.push({ X: right, Y: bottom });
+
+ points.push({ X: (right + left) / 2, Y: top });
+ points.push({ X: (right + left) / 2, Y: top });
points.push({ X: (right + left) / 2, Y: top });
+ points.push({ X: (right + left) / 2, Y: top });
+
+ points.push({ X: left, Y: bottom });
points.push({ X: left, Y: bottom });
+
+
return points;
case "circle":
const centerX = (right + left) / 2;
@@ -256,6 +277,7 @@ export namespace InteractionUtils {
// points.push({ X: x2, Y: y2 });
// return points;
case "line":
+
points.push({ X: left, Y: top });
points.push({ X: right, Y: bottom });
return points;
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index f1e6155d2..cb0a4bea0 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -134,7 +134,7 @@ export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
Scripting.addGlobal(constructor);
}
-const _scriptingGlobals: { [name: string]: any } = {};
+export const _scriptingGlobals: { [name: string]: any } = {};
let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
const _scriptingDescriptions: { [name: string]: any } = {};
const _scriptingParams: { [name: string]: any } = {};
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 0a01d8ac7..7b2c601fe 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -29,6 +29,8 @@ export namespace SearchUtil {
rows?: number;
fq?: string;
allowAliases?: boolean;
+ "facet"?: string;
+ "facet.field"?: string;
}
export function Search(query: string, returnDocs: true, options?: SearchParams): Promise<DocSearchResult>;
export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 20d881961..05ba00331 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -12,6 +12,7 @@ export namespace SelectionManager {
@observable IsDragging: boolean = false;
SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
+
@action
SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
@@ -32,6 +33,7 @@ export namespace SelectionManager {
}
@action
DeselectDoc(docView: DocumentView): void {
+
if (manager.SelectedDocuments.get(docView)) {
manager.SelectedDocuments.delete(docView);
docView.props.whenActiveChanged(false);
@@ -40,6 +42,7 @@ export namespace SelectionManager {
}
@action
DeselectAll(): void {
+
Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments.clear();
Doc.UserDoc().activeSelection = new List<Doc>([]);
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index c1627e69f..41bce8a1b 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -1,13 +1,13 @@
@import "../views/globalCssVariables";
.settings-interface {
- background-color: whitesmoke !important;
+ //background-color: whitesmoke !important;
color: grey;
width: 450px;
height: 300px;
button {
- background: $lighter-alt-accent;
+ background: #315a96;
outline: none;
border-radius: 5px;
border: 0px;
@@ -22,91 +22,243 @@
}
}
-.settings-interface {
+.settings-title {
+ font-size: 25px;
+ font-weight: bold;
+ padding-right: 10px;
+ color: black;
+}
+
+.settings-username {
+ font-size: 14px;
+ padding-right: 15px;
+ color: black;
+ margin-top: 10px;
+}
+
+.settings-section {
display: flex;
- flex-direction: column;
+ border-bottom: 1px solid grey;
+ padding-bottom: 8px;
+ padding-top: 6px;
- button {
- width: 100%;
- align-self: center;
- background: $darker-alt-accent;
- margin-top: 4px;
+ .settings-section-title {
+ font-size: 16;
+ font-weight: bold;
+ text-align: left;
+ color: black;
+ width: 80;
+ margin-right: 50px;
}
- .delete-button {
- background: rgb(227, 86, 86);
+ &:last-child {
+ border-bottom: none;
}
+}
- .close-button {
- position: absolute;
- right: 1em;
- top: 1em;
+
+.password-content {
+ display: flex;
+
+ .password-content-inputs {
+ width: 100;
+
+ .password-inputs {
+ border: none;
+ margin-bottom: 8px;
+ width: 180;
+ color: black;
+ border-radius: 5px;
+ }
+ }
+
+ .password-content-buttons {
+ margin-left: 84px;
+ width: 100;
+
+ .password-submit {
+ margin-left: 85px;
+ }
+
+ .password-forgot {
+ margin-left: 65px;
+ margin-top: -20px;
+ white-space: nowrap;
+ }
+ }
+}
+
+.accounts-content {
+ display: flex;
+}
+
+.modes-content {
+ display: flex;
+
+ .modes-select {
+ width: 170px;
+ margin-right: 65px;
+ color: black;
+ border-radius: 5px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .modes-playground {
+ display: flex;
+
+ .playground-check {
+ margin-right: 5px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .playground-text {
+ color: black;
+ }
+ }
+}
+
+.colorFlyout {
+ margin-top: 2px;
+ margin-right: 25px;
+
+ &:hover {
cursor: pointer;
}
- .settings-heading {
- letter-spacing: .5em;
+ .colorFlyout-button {
+ width: 20px;
+ height: 20px;
+ border: 0.5px solid black;
+ border-radius: 5px;
}
+}
+.preferences-content {
+ display: flex;
+ margin-top: 4px;
- .settings-body {
+ .preferences-color {
display: flex;
- justify-content: space-between;
- margin-top: -10;
- .settings-type {
- display: flex;
- flex-direction: column;
- flex-basis: 45%;
+ .preferences-color-text {
+ color: black;
+ font-size: 11;
+ margin-top: 4;
+ margin-right: 4;
+ }
+ }
+
+ .preferences-font {
+ display: flex;
+ .preferences-font-text {
+ color: black;
+ font-size: 11;
+ margin-top: 4;
+ margin-right: 4;
}
- .settings-content {
- padding-left: 1em;
- padding-right: 1em;
- display: flex;
- flex-direction: column;
- flex-basis: 70%;
- justify-content: space-around;
- text-align: left;
-
- ::placeholder {
- color: $intermediate-color;
- }
+ .font-select {
+ width: 100px;
+ color: black;
+ font-size: 9;
+ margin-right: 6;
+ border-radius: 5px;
- input {
- border-radius: 5px;
- border: none;
- padding: 4px;
- min-width: 100%;
- margin: 2px 0;
+ &:hover {
+ cursor: pointer;
}
+ }
- .error-text {
- color: #C40233;
- }
+ .size-select {
+ width: 60px;
+ color: black;
+ font-size: 9;
+ border-radius: 5px;
- .success-text {
- color: #009F6B;
+ &:hover {
+ cursor: pointer;
}
+ }
+ }
+}
- p {
- padding: 0 0 .1em .2em;
- }
+.settings-interface {
+ display: flex;
+ flex-direction: column;
+ button {
+ width: auto;
+ align-self: center;
+ background: #252b33;
+ margin-right: 15px;
+
+ //margin-top: 4px;
+
+ &:hover {
+ background: $main-accent;
}
}
+ // .delete-button {
+ // background: rgb(227, 86, 86);
+ // }
+
+ .close-button {
+ position: absolute;
+ right: 1em;
+ top: 1em;
+ cursor: pointer;
+ }
+
+ .settings-content {
+ background: #e4e4e4;
+ border-radius: 6px;
+ padding: 10px;
+ width: 560px;
+ }
+
+ .settings-top {
+ display: flex;
+ margin-bottom: 10px;
+ }
+
+
+ .error-text {
+ color: #C40233;
+ width: 300;
+ margin-left: -20;
+ font-size: 10;
+ margin-bottom: 4;
+ margin-top: -3;
+ }
+
+ .success-text {
+ width: 300;
+ margin-left: -20;
+ font-size: 10;
+ margin-bottom: 4;
+ margin-top: -3;
+ color: #009F6B;
+ }
+
.focus-span {
text-decoration: underline;
}
h1 {
- color: $dark-color;
+ color: #121721;
text-transform: uppercase;
letter-spacing: 2px;
- font-size: 120%;
+ font-size: 19;
margin-top: 0;
+ font-weight: bold;
}
.container {
@@ -133,8 +285,6 @@
color: black;
}
-
-
}
}
@@ -151,7 +301,7 @@
.settings-interface button {
width: 100%;
font-size: 30px;
- background: #b2cef8;
+ background: #315a96;
}
.settings-interface .settings-heading {
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 90d59aa51..68ed32c0f 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,4 +1,4 @@
-import { observable, runInAction, action } from "mobx";
+import { observable, runInAction, action, computed } from "mobx";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
import { observer } from "mobx-react";
@@ -9,18 +9,25 @@ import "./SettingsManager.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Networking } from "../Network";
import { CurrentUserUtils } from "./CurrentUserUtils";
-import { Utils } from "../../Utils";
+import { Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from "../../Utils";
import { Doc } from "../../fields/Doc";
import GroupManager from "./GroupManager";
import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
-import { togglePlaygroundMode } from "../../fields/util";
+import { DocServer } from "../DocServer";
+import { BoolCast, StrCast, NumCast } from "../../fields/Types";
+import { undoBatch } from "./UndoManager";
+import { ColorState, SketchPicker } from "react-color";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
library.add(fa.faTimes);
@observer
export default class SettingsManager extends React.Component<{}> {
public static Instance: SettingsManager;
+ static _settingsStyle = addStyleSheet();
@observable private isOpen = false;
@observable private dialogueBoxOpacity = 1;
@observable private overlayOpacity = 0.4;
@@ -32,6 +39,9 @@ export default class SettingsManager extends React.Component<{}> {
private new_password_ref = React.createRef<HTMLInputElement>();
private new_confirm_ref = React.createRef<HTMLInputElement>();
+
+ @computed get backgroundColor() { return Doc.UserDoc().defaultColor; }
+
public open = action(() => {
SelectionManager.DeselectAll();
this.isOpen = true;
@@ -99,54 +109,158 @@ export default class SettingsManager extends React.Component<{}> {
@action
togglePlaygroundMode = () => {
- togglePlaygroundMode();
this.playgroundMode = !this.playgroundMode;
+ if (this.playgroundMode) DocServer.Control.makeReadOnly();
+ else DocServer.Control.makeEditable();
+
+ addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "pink !important" });
+ }
+
+ @action
+ changeMode = (e: any) => {
+ if (e.currentTarget.value === "Novice") {
+ Doc.UserDoc().noviceMode = true;
+ } else {
+ Doc.UserDoc().noviceMode = false;
+ }
+ }
+
+ @action
+ changeFontFamily = (e: any) => {
+ Doc.UserDoc().fontFamily = e.currentTarget.value;
+ }
+
+ @action
+ changeFontSize = (e: any) => {
+ Doc.UserDoc().fontSize = e.currentTarget.value;
+ }
+
+ @action @undoBatch
+ switchColor = (color: ColorState) => {
+ const val = String(color.hex);
+ Doc.UserDoc().defaultColor = val;
+ return true;
}
private get settingsInterface() {
- return (
- <div className={"settings-interface"}>
- <div className="settings-heading">
- <h1>settings</h1>
- <div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} />
- </div>
- </div>
- <div className="settings-body">
- <div className="settings-type">
- <button onClick={this.onClick} value="password">reset password</button>
- <button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
- <button onClick={this.togglePlaygroundMode}>{`${this.playgroundMode ? "Disable" : "Enable"} playground mode`}</button>
- <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
- <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
- <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
- <button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
- {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
- </button>
+
+
+ const passwordContent = <div className="password-content">
+ <div className="password-content-inputs">
+ <input className="password-inputs" type="password" placeholder="current password" ref={this.curr_password_ref} />
+ <input className="password-inputs" type="password" placeholder="new password" ref={this.new_password_ref} />
+ <input className="password-inputs" type="password" placeholder="confirm new password" ref={this.new_confirm_ref} />
+ </div>
+ <div className="password-content-buttons">
+ {this.errorText ? <div className="error-text">{this.errorText}</div> : undefined}
+ {this.successText ? <div className="success-text">{this.successText}</div> : undefined}
+ <button className="password-submit" onClick={this.dispatchRequest}>submit</button>
+ <a className="password-forgot" style={{ marginLeft: 65, marginTop: -20 }}
+ href="/forgotPassword">forgot password?</a>
+ </div>
+ </div>;
+
+ const modesContent = <div className="modes-content">
+ <select className="modes-select"
+ onChange={e => this.changeMode(e)}>
+ <option key={"Novice"} value={"Novice"} selected={BoolCast(Doc.UserDoc().noviceMode)}>
+ Novice
+ </option>
+ <option key={"Developer"} value={"Developer"} selected={!BoolCast(Doc.UserDoc().noviceMode)}>
+ Developer
+ </option>
+ </select>
+ <div className="modes-playground">
+ <input className="playground-check" type="checkbox"
+ checked={this.playgroundMode}
+ onChange={undoBatch(action(() => this.togglePlaygroundMode()))}
+ /><div className="playground-text">Playground Mode</div>
+ </div>
+ </div>;
+
+ const accountsContent = <div className="accounts-content">
+ <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
+ <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
+ <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
+ </div>;
+
+ const colorBox = <SketchPicker onChange={this.switchColor}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
+ '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
+ '#FFFFFF', '#f1efeb', 'transparent']}
+ color={StrCast(this.backgroundColor)} />;
+
+ const colorFlyout = <div className="colorFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={colorBox}>
+ <div>
+ <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }}
+ onPointerDown={e => e.stopPropagation()} >
+ <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
</div>
- {this.settingsContent === "password" ?
- <div className="settings-content">
- <input placeholder="current password" ref={this.curr_password_ref} />
- <input placeholder="new password" ref={this.new_password_ref} />
- <input placeholder="confirm new password" ref={this.new_confirm_ref} />
- {this.errorText ? <div className="error-text">{this.errorText}</div> : undefined}
- {this.successText ? <div className="success-text">{this.successText}</div> : undefined}
- <button onClick={this.dispatchRequest}>submit</button>
- <a href="/forgotPassword">forgot password?</a>
-
- </div>
- : undefined}
- {this.settingsContent === "data" ?
- <div className="settings-content">
- <p>WARNING: <br />
- THIS WILL ERASE ALL YOUR CURRENT DOCUMENTS STORED ON DASH. IF YOU WISH TO PROCEED, CLICK THE BUTTON BELOW.</p>
- <button className="delete-button">DELETE</button>
- </div>
- : undefined}
</div>
+ </Flyout>
+ </div>;
+ const fontFamilies: string[] = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
+ const fontSizes: string[] = ["7pt", "8pt", "9pt", "10pt", "12pt", "14pt", "16pt", "18pt", "20pt", "24pt", "32pt", "48pt", "72pt"];
+
+ const preferencesContent = <div className="preferences-content">
+ <div className="preferences-color">
+ <div className="preferences-color-text">Background Color</div> {colorFlyout}
</div>
- );
+ <div className="preferences-font">
+ <div className="preferences-font-text">Default Font</div>
+ <select className="font-select"
+ onChange={e => this.changeFontFamily(e)}>
+ {fontFamilies.map((font) => {
+ return <option key={font} value={font} selected={StrCast(Doc.UserDoc().fontFamily) === font}>
+ {font}
+ </option>;
+ })}
+ </select>
+ <select className="size-select"
+ onChange={e => this.changeFontSize(e)}>
+ {fontSizes.map((size) => {
+ return <option key={size} value={size} selected={StrCast(Doc.UserDoc().fontSize) === size}>
+ {size}
+ </option>;
+ })}
+ </select>
+ </div>
+ </div>;
+
+ return (<div className="settings-interface">
+ <div className="settings-top">
+ <div className="settings-title">Settings</div>
+ <div className="settings-username">{Doc.CurrentUserEmail}</div>
+ <button onClick={() => window.location.assign(Utils.prepend("/logout"))}
+ style={{ right: 35, position: "absolute" }} >
+ {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
+ </button>
+ <div className="close-button" onClick={this.close}>
+ <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} />
+ </div>
+ </div>
+ <div className="settings-content">
+ <div className="settings-section">
+ <div className="settings-section-title">Password</div>
+ <div className="settings-section-context">{passwordContent}</div>
+ </div>
+ <div className="settings-section">
+ <div className="settings-section-title">Modes</div>
+ <div className="settings-section-context">{modesContent}</div>
+ </div>
+ <div className="settings-section">
+ <div className="settings-section-title">Accounts</div>
+ <div className="settings-section-context">{accountsContent}</div>
+ </div>
+ <div className="settings-section" style={{ paddingBottom: 4 }}>
+ <div className="settings-section-title">Preferences</div>
+ <div className="settings-section-context">{preferencesContent}</div>
+ </div>
+ </div>
+ </div>);
}
render() {
@@ -156,6 +270,7 @@ export default class SettingsManager extends React.Component<{}> {
isDisplayed={this.isOpen}
interactive={true}
closeOnExternalClick={this.close}
+ dialogueBoxStyle={{ width: "600px", height: "340px" }}
/>
);
}
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 130785672..7912db74d 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -1,6 +1,6 @@
.sharing-interface {
width: 600px;
- height: 360px;
+ // height: 360px;
.overlay {
transform: translate(-20px, -20px);
@@ -23,33 +23,51 @@
z-index: 999;
}
- .share-setup {
- display: flex;
- margin-bottom: 20px;
- align-items: center;
- height: 36;
+ .share-container {
+ .share-setup {
+ display: flex;
+ margin-bottom: 20px;
+ align-items: center;
+ height: 36;
- .user-search {
- width: 90%;
+ .user-search {
+ width: 90%;
- input {
- height: 30;
+ input {
+ height: 30;
+ }
+ }
+
+ .permissions-select {
+ z-index: 1;
+ margin-left: -100;
+ border: none;
+ outline: none;
+ text-align: justify; // for Edge
+ text-align-last: end;
}
- }
- .permissions-select {
- z-index: 1;
- margin-left: -100;
- border: none;
- outline: none;
- text-align: justify; // for Edge
- text-align-last: end;
+ .share-button {
+ height: 105%;
+ margin-left: 2%;
+ background-color: black;
+ }
}
- .share-button {
- height: 105%;
- margin-left: 2%;
- background-color: #979797;
+ .sort-checkboxes {
+ float: left;
+ margin-top: -17px;
+ margin-bottom: 10px;
+ font-size: 10px;
+
+ input {
+ height: 10px;
+ }
+
+ label {
+ font-weight: normal;
+ font-style: italic;
+ }
}
}
@@ -66,6 +84,7 @@
.user-sort {
text-align: left;
margin-left: 10;
+ width: 100px;
cursor: pointer;
}
@@ -92,10 +111,8 @@
height: 250px;
margin: 0 2;
-
.none {
font-style: italic;
-
}
}
}
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 452a58d21..d50a132f8 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,14 +1,13 @@
import { observable, runInAction, action } from "mobx";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocListCastAsync } from "../../fields/Doc";
+import { Doc, Opt, AclAdmin, AclPrivate, DocListCast } from "../../fields/Doc";
import { DocServer } from "../DocServer";
import { Cast, StrCast } from "../../fields/Types";
import * as RequestPromise from "request-promise";
import { Utils } from "../../Utils";
import "./SharingManager.scss";
import { observer } from "mobx-react";
-import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { DocumentView } from "../views/nodes/DocumentView";
import { SelectionManager } from "./SelectionManager";
@@ -20,17 +19,22 @@ import GroupMemberView from "./GroupMemberView";
import Select from "react-select";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { List } from "../../fields/List";
-import { distributeAcls, SharingPermissions } from "../../fields/util";
+import { distributeAcls, SharingPermissions, GetEffectiveAcl } from "../../fields/util";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
+import { library } from "@fortawesome/fontawesome-svg-core";
+
+library.add(fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown);
-library.add(fa.faCopy, fa.faTimes);
export interface User {
email: string;
userDocumentId: string;
}
-interface GroupOptions {
+/**
+ * Interface for grouped options for the react-select component.
+ */
+interface GroupedOptions {
label: string;
options: UserOptions[];
}
@@ -39,9 +43,13 @@ interface GroupOptions {
// const PublicKey = "publicLinkPermissions";
// const DefaultColor = "black";
-const groupType = "!groupType/";
+// used to differentiate between individuals and groups when sharing
const indType = "!indType/";
+const groupType = "!groupType/";
+/**
+ * A user who also has a notificationDoc.
+ */
interface ValidatedUser {
user: User;
notificationDoc: Doc;
@@ -52,42 +60,45 @@ const storage = "data";
@observer
export default class SharingManager extends React.Component<{}> {
public static Instance: SharingManager;
- @observable private isOpen = false;
- @observable private users: ValidatedUser[] = [];
- @observable private targetDoc: Doc | undefined;
- @observable private targetDocView: DocumentView | undefined;
+ @observable private isOpen = false; // whether the SharingManager modal is open or not
+ @observable private users: ValidatedUser[] = []; // the list of users with notificationDocs
+ @observable private targetDoc: Doc | undefined; // the document being shared
+ @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared
// @observable private copied = false;
- @observable private dialogueBoxOpacity = 1;
- @observable private overlayOpacity = 0.4;
- @observable private selectedUsers: UserOptions[] | null = null;
- @observable private permissions: SharingPermissions = SharingPermissions.Edit;
- @observable private individualSort: "ascending" | "descending" | "none" = "none";
- @observable private groupSort: "ascending" | "descending" | "none" = "none";
- private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
-
-
+ @observable private dialogueBoxOpacity = 1; // for the modal
+ @observable private overlayOpacity = 0.4; // for the modal
+ @observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
+ @observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
+ @observable private individualSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of individuals
+ @observable private groupSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of groups
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
+ // 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 get linkVisible() {
// return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
// }
public open = (target: DocumentView) => {
- SelectionManager.DeselectAll();
- this.populateUsers().then(action(() => {
+ runInAction(() => this.users = []);
+ // SelectionManager.DeselectAll();
+ this.populateUsers();
+ runInAction(() => {
this.targetDocView = target;
this.targetDoc = target.props.Document;
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = true;
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.users = [];
- this.selectedUsers = null;
-
+ this.selectedUsers = null; // resets the list of users and seleected users (in the react-select component)
+ TaskCompletionBox.taskCompleted = false;
setTimeout(action(() => {
// this.copied = false;
DictationOverlay.Instance.hasActiveModal = false;
@@ -100,109 +111,131 @@ export default class SharingManager extends React.Component<{}> {
SharingManager.Instance = this;
}
+ /**
+ * Populates the list of users.
+ */
+ componentDidMount() {
+ this.populateUsers();
+ }
+
+ /**
+ * Populates the list of validated users (this.users) by adding registered users which have a sidebar-sharing.
+ */
populateUsers = async () => {
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
- const raw = JSON.parse(userList) as User[];
- const evaluating = raw.map(async user => {
- const isCandidate = user.email !== Doc.CurrentUserEmail;
- if (isCandidate) {
- const userDocument = await DocServer.GetRefField(user.userDocumentId);
- if (userDocument instanceof Doc) {
- const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc);
- runInAction(() => {
- if (notificationDoc instanceof Doc) {
- this.users.push({ user, notificationDoc });
- }
- });
+ if (!this.populating) {
+ this.populating = true;
+ runInAction(() => this.users = []);
+ const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const raw = JSON.parse(userList) as User[];
+ const evaluating = raw.map(async user => {
+ const isCandidate = user.email !== Doc.CurrentUserEmail;
+ if (isCandidate) {
+ const userDocument = await DocServer.GetRefField(user.userDocumentId);
+ if (userDocument instanceof Doc) {
+ const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc);
+ runInAction(() => {
+ if (notificationDoc instanceof Doc) {
+ this.users.push({ user, notificationDoc });
+ }
+ });
+ }
}
- }
- });
- return Promise.all(evaluating);
+ });
+ return Promise.all(evaluating).then(() => this.populating = false);
+ }
}
- setInternalGroupSharing = (group: Doc, permission: string) => {
+ /**
+ * Sets the permission on the target for the group.
+ * @param group
+ * @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 = this.targetDoc!;
+ const target = targetDoc || this.targetDoc!;
const ACL = `ACL-${StrCast(group.groupName)}`;
- // fix this - not needed (here and setinternalsharing and removegroup)
- // target[ACL] = permission;
- // Doc.GetProto(target)[ACL] = permission;
- distributeAcls(ACL, permission as SharingPermissions, this.targetDoc!);
+ target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target);
- group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
+ // 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]);
users.forEach(({ notificationDoc }) => {
- DocListCastAsync(notificationDoc[storage]).then(resolved => {
- if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
- else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
- });
+ 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
});
}
+ /**
+ * Shares the documents shared with a group with a new user who has been added to that group.
+ * @param group
+ * @param emailId
+ */
shareWithAddedMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
- if (group.docsShared) {
- DocListCastAsync(group.docsShared).then(docsShared => {
- docsShared?.forEach(doc => {
- DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
- });
- });
- }
+ if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
}
+ 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);
+ }
+
+ /**
+ * Removes the documents shared with a user through a group when the user is removed from the group.
+ * @param group
+ * @param emailId
+ */
removeMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
if (group.docsShared) {
- DocListCastAsync(group.docsShared).then(docsShared => {
- docsShared?.forEach(doc => {
- DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
- });
+ DocListCast(group.docsShared).forEach(doc => {
+ Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc); // remove the doc only if it is in the list
});
}
}
+ /**
+ * Removes a group's permissions from documents that have been shared with it.
+ * @param group
+ */
removeGroup = (group: Doc) => {
if (group.docsShared) {
- DocListCastAsync(group.docsShared).then(resolved => {
- resolved?.forEach(doc => {
- const ACL = `ACL-${StrCast(group.groupName)}`;
- // doc[ACL] = doc[DataSym][ACL] = "Not Shared";
-
- distributeAcls(ACL, SharingPermissions.None, doc);
+ DocListCast(group.docsShared).forEach(doc => {
+ const ACL = `ACL-${StrCast(group.groupName)}`;
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+ distributeAcls(ACL, SharingPermissions.None, doc);
- users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc));
- });
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+ users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc));
});
}
}
- // @action
- setInternalSharing = (recipient: ValidatedUser, permission: string) => {
+ /**
+ * Shares the document with a user.
+ */
+ setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {
const { user, notificationDoc } = recipient;
- const target = this.targetDoc!;
+ const target = targetDoc || this.targetDoc!;
const key = user.email.replace('.', '_');
const ACL = `ACL-${key}`;
- distributeAcls(ACL, permission as SharingPermissions, this.targetDoc!);
+
+ target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target);
if (permission !== SharingPermissions.None) {
- DocListCastAsync(notificationDoc[storage]).then(resolved => {
- Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
- });
+ Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
}
else {
- DocListCastAsync(notificationDoc[storage]).then(resolved => {
- Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
- });
+ Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
}
}
@@ -230,14 +263,17 @@ 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 => {
- return (
- <option key={permission} value={permission} selected={permission === SharingPermissions.Edit}>
+ return Object.values(SharingPermissions).map(permission =>
+ (
+ <option key={permission} value={permission}>
{permission}
</option>
- );
- });
+ )
+ );
}
private focusOn = (contents: string) => {
@@ -270,16 +306,25 @@ export default class SharingManager extends React.Component<{}> {
);
}
+ /**
+ * Handles changes in the users selected in react-select
+ */
@action
handleUsersChange = (selectedOptions: any) => {
this.selectedUsers = selectedOptions as UserOptions[];
}
+ /**
+ * Handles changes in the permission chosen to share with someone with
+ */
@action
handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
this.permissions = event.currentTarget.value as SharingPermissions;
}
+ /**
+ * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
+ */
@action
share = () => {
if (this.selectedUsers) {
@@ -294,7 +339,7 @@ export default class SharingManager extends React.Component<{}> {
const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
TaskCompletionBox.popupX = left - 1.5 * width;
- TaskCompletionBox.popupY = top - height;
+ TaskCompletionBox.popupY = top - 1.5 * height;
TaskCompletionBox.textDisplayed = "Document shared!";
TaskCompletionBox.taskCompleted = true;
setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
@@ -303,85 +348,133 @@ export default class SharingManager extends React.Component<{}> {
}
}
+ /**
+ * Sorting algorithm to sort users.
+ */
sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
const { email: e1 } = u1.user;
const { email: e2 } = u2.user;
return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
}
+ /**
+ * Sorting algorithm to sort groups.
+ */
sortGroups = (group1: Doc, group2: Doc) => {
const g1 = StrCast(group1.groupName);
const g2 = StrCast(group2.groupName);
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
}
+ /**
+ * @returns the main interface of the SharingManager.
+ */
private get sharingInterface() {
const groupList = GroupManager.Instance?.getAllGroups() || [];
- const sortedUsers = this.users.sort(this.sortUsers)
+ const sortedUsers = this.users.slice().sort(this.sortUsers)
.map(({ user: { email } }) => ({ label: email, value: indType + email }));
- const sortedGroups = groupList.sort(this.sortGroups)
+ const sortedGroups = groupList.slice().sort(this.sortGroups)
.map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
- const options: GroupOptions[] = GroupManager.Instance ?
- [
- {
+ // the next block handles the users shown (individuals/groups/both)
+ const options: GroupedOptions[] = [];
+ if (GroupManager.Instance) {
+ if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) {
+ options.push({
label: 'Individuals',
options: sortedUsers
},
- {
+ {
+ label: 'Groups',
+ options: sortedGroups
+ });
+ }
+ else if (this.showUserOptions) {
+ options.push({
+ label: 'Individuals',
+ options: sortedUsers
+ });
+ }
+ else {
+ options.push({
label: 'Groups',
options: sortedGroups
- }
- ]
- : [];
+ });
+ }
+ }
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 effectiveAcl = this.targetDoc ? GetEffectiveAcl(this.targetDoc) : AclPrivate;
+
+ // 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}`], SharingPermissions.None);
+ const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`]);
- return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : (
+ return !permissions || user.email === this.targetDoc?.author ? null : (
<div
key={userKey}
className={"container"}
>
<span className={"padding"}>{user.email}</span>
<div className="edit-actions">
- <select
- className={"permissions-dropdown"}
- value={permissions}
- onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
- >
- {this.sharingOptions}
- </select>
+ {effectiveAcl === AclAdmin ? (
+ <select
+ className={"permissions-dropdown"}
+ value={permissions}
+ onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
+ >
+ {this.sharingOptions}
+ </select>
+ ) : (
+ <div className={"permissions-dropdown"}>
+ {permissions}
+ </div>
+ )}
</div>
</div>
);
});
+ // 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}</span>
+ <span className={"padding"}>{this.targetDoc?.author === Doc.CurrentUserEmail ? "Me" : this.targetDoc?.author}</span>
<div className="edit-actions">
<div className={"permissions-dropdown"}>
Owner
</div>
</div>
</div>
- )
+ ),
+ this.targetDoc?.author !== Doc.CurrentUserEmail ?
+ (
+ <div
+ key={"me"}
+ className={"container"}
+ >
+ <span className={"padding"}>Me</span>
+ <div className="edit-actions">
+ <div className={"permissions-dropdown"}>
+ {this.targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]}
+ </div>
+ </div>
+ </div>
+ ) : null
);
+ // the list of groups shared with
const groupListContents = groups.map(group => {
- const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None);
+ const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`]);
- return permissions === SharingPermissions.None ? null : (
+ return !permissions ? null : (
<div
key={StrCast(group.groupName)}
className={"container"}
@@ -403,7 +496,7 @@ export default class SharingManager extends React.Component<{}> {
);
});
- const displayUserList = !userListContents?.every(user => user === null);
+ // don't display the group list if all groups are null
const displayGroupList = !groupListContents?.every(group => group === null);
return (
@@ -447,10 +540,9 @@ export default class SharingManager extends React.Component<{}> {
<div className="sharing-contents">
<p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
<div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+ <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
- {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
- :
+ {<div className="share-container">
<div className="share-setup">
<Select
className={"user-search"}
@@ -460,40 +552,45 @@ export default class SharingManager extends React.Component<{}> {
options={options}
onChange={this.handleUsersChange}
value={this.selectedUsers}
+ styles={{
+ indicatorSeparator: () => ({
+ visibility: "hidden"
+ })
+ }}
/>
- <select className="permissions-select" onChange={this.handlePermissionsChange}>
+ <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
{this.sharingOptions}
</select>
<button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
Share
</button>
</div>
+ <div className="sort-checkboxes">
+ <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>
}
<div className="main-container">
<div className={"individual-container"}>
<div
className="user-sort"
onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
- Individuals {this.individualSort === "ascending" ? "↑" : this.individualSort === "descending" ? "↓" : ""} {/* → */}
+ 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"} />}
</div>
- <div className={"users-list"} style={{ display: !displayUserList ? "flex" : "block" }}>{/*200*/}
- {
- !displayUserList ?
- <div
- className={"none"}
- >
- There are no users this document has been shared with.
- </div>
- :
- userListContents
- }
+ <div className={"users-list"} style={{ display: "block" }}>{/*200*/}
+ {userListContents}
</div>
</div>
<div className={"group-container"}>
<div
className="user-sort"
onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Groups {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
+ 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"} />}
</div>
<div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/}
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 08aec3724..ab6c94f83 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -187,6 +187,11 @@ declare class List<T extends Field> extends ObjectField {
[Copy](): ObjectField;
}
+declare class InkField extends ObjectField {
+ constructor(data:Array<{X:number, Y:number}>);
+ [Copy](): ObjectField;
+}
+
// @ts-ignore
declare const console: any;
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 3717a2923..c379549d0 100644
--- a/src/client/views/.DS_Store
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 86e0a568a..7467bc043 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -7,7 +7,7 @@
box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
flex-direction: column;
background: whitesmoke;
- padding-top: 10px;
+ padding-top: 10px;
padding-bottom: 10px;
border-radius: 15px;
border: solid #BBBBBBBB 1px;
@@ -72,6 +72,7 @@
margin-left: 5px;
}
}
+
.contextMenu-description {
// width: 11vw; //10vw
background: whitesmoke;
@@ -100,6 +101,8 @@
border-color: $intermediate-color; // rgb(187, 186, 186);
border-bottom-style: solid;
border-top-style: solid;
+
+ cursor: pointer;
}
.contextMenu-itemSelected {
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 81432968d..7e233ec04 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -80,7 +80,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
render() {
if ("event" in this.props) {
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onClick={this.handleEvent}>
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}>
{this.props.icon ? (
<span className="icon-background">
<FontAwesomeIcon icon={this.props.icon} size="sm" />
@@ -95,7 +95,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
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" : "";
const submenu = !this.overItem ? (null) :
- <div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px", marginTop }}>
+ <div className="contextMenu-subMenu-cont" style={{ marginLeft: "90%", left: "0px", marginTop }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
if (!("noexpand" in this.props)) {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9fd406407..831c246d1 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast, AclAdmin } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { List } from '../../fields/List';
import { DateField } from '../../fields/DateField';
import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl, getPlaygroundMode, SharingPermissions } from '../../fields/util';
+import { GetEffectiveAcl, SharingPermissions, distributeAcls } from '../../fields/util';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -26,7 +26,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
@@ -59,7 +59,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
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
@@ -96,7 +96,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
[AclAddonly, SharingPermissions.Add],
- [AclEdit, SharingPermissions.Edit]
+ [AclEdit, SharingPermissions.Edit],
+ [AclAdmin, SharingPermissions.Admin]
]);
lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
@@ -114,7 +115,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
return style;
}
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
_annotationKey: string = "annotations";
public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
@@ -150,25 +151,24 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
if (added.length) {
- if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
+ if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
return false;
}
else {
if (this.props.Document[AclSym]) {
added.forEach(d => {
- const dataDoc = d[DataSym];
- dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- dataDoc[key] = d[key] = this.AclMap.get(value);
+ distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
}
});
}
+
if (effectiveAcl === AclAddonly) {
added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
}
else {
added.map(doc => doc.context = this.props.Document);
- targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
+ (targetDataDoc[this.annotationKey] as List<Doc>).push(...added);
targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
}
}
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index c2ca93900..09ae14016 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -64,9 +64,13 @@ $linkGap : 3px;
text-align: center;
border-radius: 50%;
pointer-events: auto;
- color: $dark-color;
- border: $dark-color 1px solid;
+ background-color: $dark-color;
+ border: none;
transition: 0.2s ease all;
+
+ &:hover {
+ background-color: $main-accent;
+ }
}
.documentButtonBar-linker:hover {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c99034d81..8748b1880 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -198,7 +198,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
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></>}>
<div className="documentButtonBar-linker"
- style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
+ style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white", border: isPinned ? "black 1px solid " : "" }}
onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
/>
@@ -276,12 +276,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const considerPush = isText && this.considerGoogleDocsPush;
return <div className="documentButtonBar">
<div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+ <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
</div>
- {DocumentLinksButton.StartLink ? <div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
- </div> : null}
- <div className="documentButtonBar-button">
+ {DocumentLinksButton.StartLink || !Doc.UserDoc()["documentLinksButton-hideEnd"] ? <div className="documentButtonBar-button">
+ <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+ </div> : (null)}
+ {/* <div className="documentButtonBar-button">
{this.templateButton}
</div>
<div className="documentButtonBar-button">
@@ -289,16 +289,16 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
</div>
<div className="documentButtonBar-button">
{this.contextButton}
- </div>
+ </div> */}
<div className="documentButtonBar-button">
{this.pinButton}
</div>
- <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
+ {/* <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
{this.considerGoogleDocsPush}
</div>
<div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
{this.considerGoogleDocsPull}
- </div>
+ </div> */}
</div>;
}
}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 424a06431..5401623e8 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -24,8 +24,7 @@ $linkGap : 3px;
.documentDecorations-rotation {
pointer-events: auto;
- // cursor: grabbing;
- cursor: ns-resize;
+ cursor: alias;
width: 10px;
height: 10px;
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8d63537e7..f1169763e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,9 +1,9 @@
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 } from '@fortawesome/free-solid-svg-icons';
+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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, get } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DataSym, Field, WidthSym, HeightSym } from "../../fields/Doc";
+import { Doc, DataSym, Field, WidthSym, HeightSym, AclEdit, AclAdmin } from "../../fields/Doc";
import { Document } from '../../fields/documentSchemas';
import { ScriptField } from '../../fields/ScriptField';
import { Cast, StrCast, NumCast } from "../../fields/Types";
@@ -21,8 +21,14 @@ import e = require('express');
import { CollectionDockingView } from './collections/CollectionDockingView';
import { SnappingManager } from '../util/SnappingManager';
import { HtmlField } from '../../fields/HtmlField';
-import { InkData, InkField, InkTool } from "../../fields/InkField";
+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);
@@ -44,6 +50,7 @@ library.add(faAngleDoubleRight);
library.add(faAngleLeft);
library.add(faAngleRight);
library.add(faPause);
+library.add(faExternalLinkAlt);
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -54,11 +61,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _linkBoxHeight = 20 + 3; // link button height + margin
private _titleHeight = 20;
private _resizeUndo?: UndoManager.Batch;
+ private _rotateUndo?: UndoManager.Batch;
private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
private _snapX = 0; _snapY = 0; // last snapped location of resize border
private _prevX = 0;
private _prevY = 0;
private _centerPoints: { X: number, Y: number }[] = [];
+ private _inkDocs: { x: number, y: number, width: number, height: number }[] = [];
@observable private _accumulatedTitle = "";
@observable private _titleControlString: string = "#title";
@@ -88,7 +97,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
var [sptX, sptY] = transform.transformPoint(0, 0);
let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
- if (documentView.props.Document.type === DocumentType.LINK) {
+ if (documentView.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
const docuBox = documentView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
if (docuBox.length) {
const rect = docuBox[0].getBoundingClientRect();
@@ -151,8 +160,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (e.button === 0 && !e.altKey && !e.ctrlKey) {
let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
while (child.children.length) {
- const next = Array.from(child.children).find(c => typeof (c.className) !== "string");
- if (typeof (next?.className) === "string" && next?.className.includes("documentView-node")) break;
+ const next = Array.from(child.children).find(c => typeof (c.className) === "string");
+ if (next?.className.includes("documentView-node")) break;
if (next) child = next;
else break;
}
@@ -193,8 +202,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
SelectionManager.DeselectAll();
selected.map(dv => {
- recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true);
- dv.props.removeDocument?.(dv.props.Document);
+ const effectiveAcl = GetEffectiveAcl(dv.props.Document);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete
+ recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true);
+ dv.props.removeDocument?.(dv.props.Document);
+ }
});
}
}
@@ -249,8 +261,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return false;
}
+ @undoBatch
@action
onRotateDown = (e: React.PointerEvent): void => {
+ this._rotateUndo = UndoManager.StartBatch("rotatedown");
+
setupMoveUpEvents(this, e, this.onRotateMove, this.onRotateUp, (e) => { });
this._prevX = e.clientX;
this._prevY = e.clientY;
@@ -272,6 +287,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}));
}
+
+ @undoBatch
@action
onRotateMove = (e: PointerEvent, down: number[]): boolean => {
@@ -309,8 +326,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- doc._height = (bottom - top) * element.props.ScreenToLocalTransform().Scale;
- doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale;
+ // doc._height = (bottom - top) * element.props.ScreenToLocalTransform().Scale;
+ // doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale;
+ doc._height = (bottom - top);
+ doc._width = (right - left);
}
index++;
@@ -321,14 +340,30 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
onRotateUp = (e: PointerEvent) => {
this._centerPoints = [];
+ this._rotateUndo?.end();
+ this._rotateUndo = undefined;
}
_initialAutoHeight = false;
_dragHeights = new Map<Doc, number>();
+
@action
onPointerDown = (e: React.PointerEvent): void => {
+
+ this._inkDocs = [];
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+ 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) {
+ doc._nativeHeight = doc._height;
+ doc._nativeWidth = doc._width;
+ }
+ }
+ }));
+
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
if (e.button === 0) {
this._resizeHdlId = e.currentTarget.id;
@@ -352,7 +387,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
const first = SelectionManager.SelectedDocuments()[0];
let thisPt = { thisX: e.clientX - this._offX, thisY: e.clientY - this._offY };
- const fixedAspect = first.layoutDoc._nativeWidth ? NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight) : 0;
+ 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) {
+ fixedAspect = NumCast(doc._nativeWidth) / NumCast(doc._nativeHeight);
+ }
+ }));
+
+
if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
const project = (p: number[], a: number[], b: number[]) => {
const atob = [b[0] - a[0], b[1] - a[1]];
@@ -374,7 +417,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
move[1] = thisPt.thisY - this._snapY;
this._snapX = thisPt.thisX;
this._snapY = thisPt.thisY;
-
+ let dragBottom = false;
let dX = 0, dY = 0, dW = 0, dH = 0;
const unfreeze = () =>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
@@ -412,6 +455,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
case "documentDecorations-bottomResizer":
unfreeze();
dH = move[1];
+ dragBottom = true;
break;
case "documentDecorations-leftResizer":
unfreeze();
@@ -438,7 +482,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (nwidth / nheight !== width / height) {
height = nheight / nwidth * width;
}
- if (!e.ctrlKey) {
+ if (!e.ctrlKey && (!dragBottom || !element.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
else dW = dH * nwidth / nheight;
}
@@ -473,7 +517,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH;
}
else {
- if (!fixedAspect || e.ctrlKey) {
+ if (!fixedAspect || e.ctrlKey || (dragBottom && element.layoutDoc._fitWidth)) {
doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0);
}
doc._height = actualdH;
@@ -504,6 +548,28 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
(e.button === 0) && this._resizeUndo?.end();
this._resizeUndo = undefined;
SnappingManager.clearSnapLines();
+
+
+ //need to change points for resize, or else rotation/control points will fail.
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView, index) => {
+ const doc = Document(element.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ ink.forEach(i => {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = ((doc.x || 0) - this._inkDocs[index].x) + (i.X * (doc._width || 0)) / this._inkDocs[index].width;
+ 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._nativeWidth = 0;
+ doc._nativeHeight = 0;
+ }
+ }));
}
@computed
@@ -545,17 +611,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return (null);
}
+ const canDelete = SelectionManager.SelectedDocuments().some(docView => {
+ const docAcl = GetEffectiveAcl(docView.props.Document);
+ const collectionAcl = GetEffectiveAcl(docView.props.ContainingCollectionDoc);
+ return [docAcl, collectionAcl].some(acl => [AclAdmin, AclEdit].includes(acl));
+ });
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>) : (
- <Tooltip title={<><div className="dash-tooltip">Delete</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>);
+ </div></Tooltip>) : canDelete ? (
+ <Tooltip title={<><div className="dash-tooltip">Delete</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 titleArea = this._edtingTitle ?
<>
@@ -594,6 +665,15 @@ 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;
+ var rotButton = <></>;
+ //make offset larger for ink to edit points
+ if (seldoc.rootDoc.type === DocumentType.INK) {
+ offset = 20;
+ rotButton = <div id="documentDecorations-rotation" title="rotate" className="documentDecorations-rotation"
+ onPointerDown={this.onRotateDown}> ⟲ </div>;
+ }
+
return (<div className="documentDecorations" style={{ background: darkScheme }} >
<div className="documentDecorations-background" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
@@ -606,10 +686,10 @@ 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) + "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,
+ 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,
}}>
{maximizeIcon}
{titleArea}
@@ -619,10 +699,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
{"_"}
</div></Tooltip>}
<Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}>
- {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+ {SelectionManager.SelectedDocuments().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."}
</div></Tooltip>
- <div id="documentDecorations-rotation" className="documentDecorations-rotation"
- onPointerDown={this.onRotateDown}> ⟲ </div>
+ {rotButton}
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topResizer" className="documentDecorations-resizer"
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index ad61d3f91..ec3e754fb 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -6,6 +6,8 @@ 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';
export interface EditableProps {
/**
@@ -52,6 +54,10 @@ export interface EditableProps {
color?: string | undefined;
onDrop?: any;
placeholder?: string;
+ highlight?: boolean;
+ positions?: number[];
+ search?: string;
+ bing?: () => string | undefined;
}
/**
@@ -179,6 +185,34 @@ export class EditableView extends React.Component<EditableProps> {
placeholder={this.props.placeholder}
/>;
}
+
+ 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 ?
@@ -187,17 +221,14 @@ export class EditableView extends React.Component<EditableProps> {
{this.renderEditor()}
</div> : this.renderEditor();
} else {
- this.props.autosuggestProps?.resetValue();
+ 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: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
+ 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,
- color: this.props.contents ? this.props.color ? this.props.color : "black" : "grey"
- }}>
- {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+ {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>
);
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 2e588ceb5..30df7cf9a 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -22,6 +22,7 @@ import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import * as fitCurve from 'fit-curve';
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
@observer
@@ -63,7 +64,7 @@ export default class GestureOverlay extends Touchable {
private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
private _holdTimer: NodeJS.Timeout | undefined;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
constructor(props: Readonly<{}>) {
super(props);
@@ -630,6 +631,22 @@ export default class GestureOverlay extends Touchable {
// if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
+ const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
+ newPoints.pop();
+ const controlPoints: { X: number, Y: number }[] = [];
+
+ const bezierCurves = fitCurve(newPoints, 10);
+ for (const curve of bezierCurves) {
+
+ controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
+ controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
+ controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
+ controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
+
+
+ }
+ this._points = controlPoints;
+
this.dispatchGesture(GestureUtils.Gestures.Stroke);
}
this._points = [];
@@ -649,12 +666,25 @@ export default class GestureOverlay extends Touchable {
}
makePolygon = (shape: string, gesture: boolean) => {
+ //take off gesture recognition for now
+ if (gesture) {
+ return false;
+ }
const xs = this._points.map(p => p.X);
const ys = this._points.map(p => p.Y);
var right = Math.max(...xs);
var left = Math.min(...xs);
var bottom = Math.max(...ys);
var top = Math.min(...ys);
+ const firstx = this._points[0].X;
+ const firsty = this._points[0].Y;
+ const lastx = this._points[this._points.length - 2].X;
+ const lasty = this._points[this._points.length - 2].Y;
+ var fourth = (lastx - firstx) / 4;
+ if (isNaN(fourth) || fourth === 0) { fourth = 0.01; }
+ var m = (lasty - firsty) / (lastx - firstx);
+ if (isNaN(m) || m === 0) { m = 0.01; }
+ const b = firsty - m * firstx;
if (shape === "noRec") {
return false;
}
@@ -684,18 +714,55 @@ export default class GestureOverlay extends Touchable {
//must be (points[0].X,points[0]-1)
case "rectangle":
this._points.push({ X: left, Y: top });
+ this._points.push({ X: left, Y: top });
+
+ this._points.push({ X: right, Y: top });
+ this._points.push({ X: right, Y: top });
+ this._points.push({ X: right, Y: top });
this._points.push({ X: right, Y: top });
+
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
this._points.push({ X: right, Y: bottom });
+
+ this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
+
+ this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
- this._points.push({ X: left, Y: top - 1 });
+ // this._points.push({ X: left, Y: top });
+
+ // this._points.push({ X: left, Y: top });
+ // this._points.push({ X: left, Y: top });
+
+ // this._points.push({ X: left, Y: top - 1 });
break;
case "triangle":
+ // this._points.push({ X: left, Y: bottom });
+ // this._points.push({ X: right, Y: bottom });
+ // this._points.push({ X: (right + left) / 2, Y: top });
+ // this._points.push({ X: left, Y: bottom });
+ // this._points.push({ X: left, Y: bottom - 1 });
+ this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
+
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
this._points.push({ X: right, Y: bottom });
+
+ this._points.push({ X: (right + left) / 2, Y: top });
+ this._points.push({ X: (right + left) / 2, Y: top });
this._points.push({ X: (right + left) / 2, Y: top });
+ this._points.push({ X: (right + left) / 2, Y: top });
+
+ this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
- this._points.push({ X: left, Y: bottom - 1 });
+
+
break;
case "circle":
const centerX = (right + left) / 2;
@@ -712,11 +779,62 @@ export default class GestureOverlay extends Touchable {
}
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 });
+ // this._points.push({ X: centerX, Y: top });
+ // this._points.push({ X: centerX + radius / 2, Y: top });
+
+ // this._points.push({ X: right, Y: top + radius / 2 });
+ // this._points.push({ X: right, Y: top + radius });
+ // this._points.push({ X: right, Y: top + radius });
+ // this._points.push({ X: right, Y: bottom - radius / 2 });
+
+ // this._points.push({ X: right - radius / 2, Y: bottom });
+ // this._points.push({ X: right - radius, Y: bottom });
+ // this._points.push({ X: right - radius, Y: bottom });
+ // this._points.push({ X: left + radius / 2, Y: bottom });
+
+ // this._points.push({ X: left, Y: bottom - radius / 2 });
+ // this._points.push({ X: left, Y: bottom - radius });
+ // this._points.push({ X: left, Y: bottom - radius });
+ // this._points.push({ X: left, Y: top + radius / 2 });
+
+ // this._points.push({ X: left + radius / 2, Y: top });
+ // this._points.push({ X: left + radius, Y: top });
+
+
+
+
+
+
+
break;
case "line":
- this._points.push({ X: left, Y: top });
- this._points.push({ X: right, Y: bottom });
- // this._points.push({ X: right, Y: bottom - 1 });
+ // const firstx = this._points[0].X;
+ // const firsty = this._points[0].Y;
+ // const lastx = this._points[this._points.length - 1].X;
+ // const lasty = this._points[this._points.length - 1].Y;
+ // const fourth = (lastx - firstx) / 4;
+ // const m = (lasty - firsty) / (lastx - firstx);
+ // const b = firsty - m * firstx;
+ this._points.push({ X: firstx, Y: firsty });
+ this._points.push({ X: firstx, Y: firsty });
+
+ this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
+ this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
+ this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
+ this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
+
+ this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
+ this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
+ this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
+ this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
+
+ this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
+ this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
+ this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
+ this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
+
+ this._points.push({ X: firstx + 4 * fourth, Y: m * (firstx + 4 * fourth) + b });
+ this._points.push({ X: firstx + 4 * fourth, Y: m * (firstx + 4 * fourth) + b });
break;
case "arrow":
const x1 = left;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 086085db5..3a61e89ce 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,6 +1,6 @@
import { action } from "mobx";
import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc, DocListCast, AclEdit, AclAdmin } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
@@ -24,6 +24,7 @@ import PDFMenu from "./pdf/PDFMenu";
import { ContextMenu } from "./ContextMenu";
import GroupManager from "../util/GroupManager";
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
+import { GetEffectiveAcl } from "../../fields/util";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -105,7 +106,6 @@ export default class KeyManager {
}
doDeselect && SelectionManager.DeselectAll();
DictationManager.Controls.stop();
- // RecommendationsBox.Instance.closeMenu();
GoogleAuthenticationManager.Instance.cancel();
HypothesisAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
@@ -119,8 +119,18 @@ export default class KeyManager {
return { stopPropagation: false, preventDefault: false };
}
}
- UndoManager.RunInBatch(() =>
- SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
+
+ const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
+ const selected = SelectionManager.SelectedDocuments().slice();
+ UndoManager.RunInBatch(() => {
+ selected.map(dv => {
+ const effectiveAcl = GetEffectiveAcl(dv.props.Document);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete
+ recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true);
+ dv.props.removeDocument?.(dv.props.Document);
+ }
+ });
+ }, "delete");
SelectionManager.DeselectAll();
break;
case "arrowleft":
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e26ad47f9..4a77728b6 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -17,6 +17,9 @@ 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);
@@ -25,8 +28,12 @@ const InkDocument = makeInterface(documentSchema);
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+ private _controlUndo?: UndoManager.Batch;
+
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
+
+
private analyzeStrokes = () => {
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
@@ -45,6 +52,41 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
FormatShapePane.Instance.Pinned = true;
}
+ private _prevX = 0;
+ private _prevY = 0;
+ private _controlNum = 0;
+ @action
+ onControlDown = (e: React.PointerEvent, i: number): void => {
+ setupMoveUpEvents(this, e, this.onControlMove, this.onControlup, (e) => { });
+ this._controlUndo = UndoManager.StartBatch("DocDecs set radius");
+ this._prevX = e.clientX;
+ this._prevY = e.clientY;
+ this._controlNum = i;
+ }
+
+ @action
+ changeCurrPoint = (i: number) => {
+ FormatShapePane.Instance._currPoint = i;
+ }
+
+ @action
+ onControlMove = (e: PointerEvent, down: number[]): boolean => {
+ const xDiff = this._prevX - e.clientX;
+ const yDiff = this._prevY - e.clientY;
+ FormatShapePane.Instance.control(xDiff, yDiff, this._controlNum);
+ this._prevX = e.clientX;
+ this._prevY = e.clientY;
+ return false;
+ }
+
+ onControlup = (e: PointerEvent) => {
+ this._prevX = 0;
+ this._prevY = 0;
+ this._controlNum = 0;
+ this._controlUndo?.end();
+ this._controlUndo = undefined;
+ }
+
public static MaskDim = 50000;
render() {
TraceMobx();
@@ -57,19 +99,78 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const top = Math.min(...ys) - strokeWidth / 2;
const right = Math.max(...xs) + strokeWidth / 2;
const bottom = Math.max(...ys) + strokeWidth / 2;
- const width = right - left;
- const height = bottom - top;
- const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
- const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
+ const width = Math.max(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.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, 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);
+
+ const controlPoints: { X: number, Y: number, I: number }[] = [];
+ const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
+ const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
+ if (data.length >= 4) {
+ for (var i = 0; i <= data.length - 4; i += 4) {
+ controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+ controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
+ handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
+ handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
+ }
+
+ handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
+ for (var i = 2; i < data.length - 4; i += 4) {
+
+ handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
+
+ }
+ handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+
+ }
+ // if (data.length <= 4) {
+ // handlePoints = [];
+ // handleLine = [];
+ // controlPoints = [];
+ // for (var i = 0; i < data.length; i++) {
+ // controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+ // }
+
+ // }
+ const dotsize = String(Math.max(width * scaleX, height * scaleY) / 40);
+
+ 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"
+ 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>);
+ const handleLines = handleLine.map((pts, i) =>
+
+ <svg height="100" width="100">
+ <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"} />
+ <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"} />
+
+ </svg>);
+
+
return (
<svg className="inkingStroke"
width={width}
@@ -85,13 +186,17 @@ 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: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
+ //cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
}
}}
><defs>
</defs>
{hpoints}
{points}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? controls : ""}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handles : ""}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handleLines : ""}
+
</svg>
);
}
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index a2a9ceca5..97ed0a901 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -26,7 +26,7 @@ body {
height: 100%;
border-radius: inherit;
position: inherit;
- // background: inherit;
+ // background: inherit;
}
p {
@@ -37,7 +37,7 @@ p {
::-webkit-scrollbar {
-webkit-appearance: none;
height: 8px;
- width: 8px;
+ width: 8px;
}
::-webkit-scrollbar-thumb {
@@ -47,7 +47,7 @@ p {
// button stuff
button {
- background: $dark-color;
+ background: black;
outline: none;
border: 0px;
color: $light-color;
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index e1ddbc533..f3fba82bc 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -21,7 +21,7 @@
// add nodes menu. Note that the + button is actually an input label, not an actual button.
.mainView-docButtons {
position: absolute;
- bottom: 20px;
+ bottom: 35px;
left: calc(100% + 5px);
z-index: 1;
}
@@ -102,6 +102,45 @@
user-select: none;
}
+.mainView-propertiesDragger {
+ //background-color: rgb(140, 139, 139);
+ background-color: lightgrey;
+ height: 55px;
+ width: 17px;
+ position: absolute;
+ top: 55%;
+ border: 1px black solid;
+ border-radius: 0;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+ border-right: unset;
+ z-index: 2;
+
+ .mainView-propertiesDragger-icon {
+ width: 10px;
+ height: 10px;
+ float: left;
+ margin-left: 5.5px;
+ padding-top: 19px;
+ }
+
+ &:hover {
+ cursor: grab;
+ }
+}
+
+.mainiView-propertiesView {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ top: 0;
+ border-left: solid 1px;
+ z-index: 100000;
+ cursor: auto;
+}
+
.mainView-flyoutContainer {
display: flex;
flex-direction: column;
@@ -114,6 +153,75 @@
}
}
+.mainView-menuPanel {
+
+ width: 60px;
+ background-color: #121721;
+ height: calc(100% - 32px);
+ //overflow-y: scroll;
+ //overflow-x: hidden;
+
+
+ .mainView-menuPanel-button {
+ padding: 7px;
+ padding-left: 7px;
+ width: 100%;
+ background: black;
+
+ .mainView-menuPanel-button-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;
+ }
+ }
+ }
+
+ .mainView-menuPanel-button-label {
+ color: white;
+ margin-left: px;
+ 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;
+ }
+
+ .mainView-menuPanel-button-icon {
+ width: auto;
+ height: 35px;
+ padding: 5px;
+ }
+
+ svg {
+ width: 95% !important;
+ height: 95%;
+ }
+}
+
+.mainView-searchPanel {
+ width: 100%;
+ height: 32px;
+ background-color: black;
+ color: white;
+ text-align: center;
+ vertical-align: middle;
+}
+
.mainView-mainDiv {
width: 100%;
height: 100%;
@@ -162,26 +270,45 @@
display: flex;
flex-direction: column;
z-index: 2;
+
+ .mainView-libraryFlyout-close {
+ right: 6;
+ top: 5;
+ position: absolute;
+ margin-right: 6px;
+ z-index: 10;
+ margin-bottom: 10;
+ }
}
.mainView-expandFlyoutButton {
position: absolute;
- top: 100px;
- right: 30px;
+ top: 120px;
+ right: 55px;
cursor: pointer;
}
.mainView-libraryHandle {
- width: 20px;
+ width: 28px;
left: calc(100% - 10px);
- height: 40px;
+ height: 55px;
top: 50%;
border: 1px solid black;
- border-radius: 5px;
+ border-radius: 8px;
position: absolute;
z-index: 2;
touch-action: none;
- cursor: ew-resize;
+ background-color: lightgrey;
+ cursor: grab;
+
+ .mainView-libraryHandle-icon {
+ width: 10px;
+ height: 10px;
+ float: right;
+ margin-right: 3px;
+ padding-top: 19px;
+ }
+
}
.mainView-workspace {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ae3f05fb7..b6058db7a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,16 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-
-import {
- faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
- faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
- faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
- faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
- faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser
-} from '@fortawesome/free-solid-svg-icons';
-import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
+import { faHireAHelper, faBuffer } 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 { observer } from 'mobx-react';
@@ -21,51 +11,56 @@ import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
+import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils, returnEmptyFilter } from '../../Utils';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { DocumentManager } from '../util/DocumentManager';
+import GroupManager from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
-import RichTextMenu from './nodes/formattedText/RichTextMenu';
import { Scripting } from '../util/Scripting';
+import { SelectionManager } from '../util/SelectionManager';
import SettingsManager from '../util/SettingsManager';
-import GroupManager from '../util/GroupManager';
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 { CollectionLinearView } from './collections/CollectionLinearView';
+import CollectionMenu from './collections/CollectionMenu';
import { CollectionView, CollectionViewType } from './collections/CollectionView';
import { ContextMenu } from './ContextMenu';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import GestureOverlay from './GestureOverlay';
+import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import KeyManager from './GlobalKeyHandler';
+import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
import { MainViewNotifs } from './MainViewNotifs';
import { AudioBox } from './nodes/AudioBox';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import RichTextMenu from './nodes/formattedText/RichTextMenu';
+import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
+import { LinkDocPreview } from './nodes/LinkDocPreview';
import { RadialMenu } from './nodes/RadialMenu';
+import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
-import { ScriptField } from '../../fields/ScriptField';
-import { TimelineMenu } from './animationtimeline/TimelineMenu';
-import { SnappingManager } from '../util/SnappingManager';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { DocumentManager } from '../util/DocumentManager';
-import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { LinkMenu } from './linking/LinkMenu';
-import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { TaskCompletionBox } from './nodes/TaskCompletedBox';
-import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
-import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
-import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
-import CollectionMenu from './collections/CollectionMenu';
+import { undoBatch } from '../util/UndoManager';
+import { SearchBox } from './search/SearchBox';
@observer
export class MainView extends React.Component {
@@ -78,16 +73,35 @@ export class MainView extends React.Component {
@observable private _panelWidth: number = 0;
@observable private _panelHeight: number = 0;
- @observable private _flyoutTranslate: boolean = true;
- @observable public flyoutWidth: number = 250;
+ @observable private _flyoutTranslate: boolean = false;
+ @observable public flyoutWidth: number = 0;
private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
@computed private get userDoc() { return Doc.UserDoc(); }
@computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
@computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
- @computed public get sidebarButtonsDoc() { return Cast(this.userDoc["tabs-buttons"], Doc) as Doc; }
+ @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;
componentDidMount() {
DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
@@ -127,6 +141,9 @@ export class MainView extends React.Component {
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;
+
configure({ enforceActions: "observed" });
if (window.location.pathname !== "/home") {
const pathname = window.location.pathname.substr(1).split("/");
@@ -135,7 +152,7 @@ export class MainView extends React.Component {
if (type === "doc") {
CurrentUserUtils.MainDocId = pathname[1];
if (!this.userDoc) {
- runInAction(() => this.flyoutWidth = 0);
+ runInAction(() => this.closeFlyout());
DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt<Field>) =>
field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
}
@@ -143,14 +160,23 @@ export class MainView extends React.Component {
}
}
- library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
- faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
- faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
- faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
- faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser);
+ library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt,
+ 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.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.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.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.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);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -165,6 +191,20 @@ export class MainView extends React.Component {
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-table" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "beta" || thing.className.toString() === "collectionSchemaView-menuOptions-wrapper") {
+ check = true;
+ }
+ });
+ if (check === false) {
+ SearchBox.Instance.closeSearch();
+ }
+ }
+
+
});
globalPointerUp = () => this.isPointerDown = false;
@@ -212,7 +252,7 @@ export class MainView extends React.Component {
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
- _width: this._panelWidth * .7,
+ _width: this._panelWidth * .7 - this.propertiesWidth() * 0.7,
_height: this._panelHeight,
title: "Collection " + workspaceCount,
};
@@ -265,7 +305,7 @@ export class MainView extends React.Component {
}
// if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
setTimeout(async () => {
- const col = this.userDoc && await Cast(this.userDoc.rightSidebarCollection, Doc);
+ const col = this.userDoc && await Cast(this.userDoc["sidebar-sharing"], Doc);
col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col);
}, 100);
return true;
@@ -278,16 +318,31 @@ export class MainView extends React.Component {
@action
onResize = (r: any) => {
- this._panelWidth = r.offset.width;
+ this._panelWidth = r.offset.width;// - this.propertiesWidth();
this._panelHeight = r.offset.height;
}
- getPWidth = () => this._panelWidth;
+
+ @action
+ getPWidth = () => this._panelWidth - this.propertiesWidth()
+
getPHeight = () => this._panelHeight;
getContentsHeight = () => this._panelHeight - this._buttonBarHeight;
- defaultBackgroundColors = (doc: Doc) => {
+ defaultBackgroundColors = (doc: Opt<Doc>) => {
+ 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(Doc.UserDoc().defaultColor);
+ }
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: {
@@ -297,6 +352,7 @@ export class MainView extends React.Component {
}
} else {
switch (doc?.type) {
+ case DocumentType.FONTICON: return "black";
case DocumentType.RTF: return "#f1efeb";
case DocumentType.BUTTON:
case DocumentType.LABEL: return "lightgray";
@@ -308,8 +364,10 @@ export class MainView extends React.Component {
}
}
}
+
@computed get mainDocView() {
- return <DocumentView Document={this.mainContainer!}
+ return <DocumentView
+ Document={this.mainContainer!}
DataDoc={undefined}
LibraryPath={emptyPath}
addDocument={undefined}
@@ -325,7 +383,6 @@ export class MainView extends React.Component {
NativeWidth={returnZero}
PanelWidth={this.getPWidth}
PanelHeight={this.getPHeight}
- renderDepth={0}
focus={emptyFunction}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
@@ -333,103 +390,67 @@ export class MainView extends React.Component {
docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
+ renderDepth={-1}
/>;
}
+
@computed get dockingContent() {
TraceMobx();
const mainContainer = this.mainContainer;
- const width = this.flyoutWidth;
- return <Measure offset onResize={this.onResize}>
- {({ measureRef }) =>
- <div ref={measureRef} className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
- {!mainContainer ? (null) : this.mainDocView}
- </div>
- }
- </Measure>;
- }
-
- _canClick = false;
- onPointerDown = (e: React.PointerEvent) => {
- if (this._flyoutTranslate) {
- this._canClick = true;
- this._flyoutSizeOnDown = e.clientX;
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- e.preventDefault();
- }
+ const width = this.flyoutWidth + this.propertiesWidth();
+ return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - 32px)` }}>
+ {!mainContainer ? (null) : this.mainDocView}
+ </div>;
}
@action
- pointerLeaveDragger = () => {
- if (!this._flyoutTranslate) {
- this.flyoutWidth = 0;
- this._flyoutTranslate = true;
- }
+ 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);
}
@action
- onPointerMove = (e: PointerEvent) => {
- this.flyoutWidth = Math.max(e.clientX, 0);
- Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false);
- this.sidebarButtonsDoc._columnWidth = this.flyoutWidth / 3 - 30;
- }
- @action
- onPointerUp = (e: PointerEvent) => {
- if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4 && this._canClick) {
- this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0;
- this.flyoutWidth && (this.sidebarButtonsDoc._columnWidth = this.flyoutWidth / 3 - 30);
+ 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();
+ }));
}
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
}
+
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);
}
- sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
- mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
+ sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
+ //sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
+ mainContainerXf = () => this.sidebarScreenToLocal().translate(-55, 0);
+ @computed get closePosition() { return 55 + this.flyoutWidth; }
@computed get flyout() {
- const sidebarContent = this.userDoc?.["tabs-panelContainer"];
- if (!(sidebarContent instanceof Doc)) {
- return (null);
- }
- return <div className="mainView-flyoutContainer" >
- <div className="mainView-tabButtons" style={{ height: `${this._buttonBarHeight - 10/*margin-top*/}px`, backgroundColor: StrCast(this.sidebarButtonsDoc.backgroundColor) }}>
- <DocumentView
- Document={this.sidebarButtonsDoc}
- DataDoc={undefined}
- LibraryPath={emptyPath}
- addDocument={undefined}
- rootSelected={returnTrue}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- removeDocument={undefined}
- onClick={undefined}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
- ContentScaling={returnOne}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={this.flyoutWidthFunc}
- PanelHeight={this.getPHeight}
- renderDepth={0}
- focus={emptyFunction}
- backgroundColor={this.defaultBackgroundColors}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>
- <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${this._buttonBarHeight}px)`, width: "100%", overflow: "visible" }}>
+ if (!this.sidebarContent) return null;
+ return <div className="mainView-libraryFlyout">
+ <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - 32px)`, width: "100%", overflow: "visible" }}>
+ {/* {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close"
+ onPointerDown={this.closeFlyout}>
+ <FontAwesomeIcon icon="times" color="black" size="lg" />
+ </div> : null} */}
+
<DocumentView
- Document={sidebarContent}
+ Document={this.sidebarContent}
DataDoc={undefined}
LibraryPath={emptyPath}
addDocument={undefined}
@@ -452,62 +473,181 @@ export class MainView extends React.Component {
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- <div className="buttonContainer" >
- <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
- <FontAwesomeIcon icon="cog" size="lg" />
- </button>
- </div>
+ ContainingCollectionDoc={undefined}
+ relative={true}
+ forcedBackgroundColor={() => "lightgrey"}
+ />
</div>
- {this.docButtons}
+ {this.docButtons}</div>;
+ }
+
+ @computed get menuPanel() {
+ return <div className="mainView-menuPanel">
+ <DocumentView
+ Document={Doc.UserDoc().menuStack as Doc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ rootSelected={returnTrue}
+ removeDocument={returnFalse}
+ onClick={undefined}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ ContentScaling={returnOne}
+ PanelWidth={() => 60}
+ PanelHeight={this.getContentsHeight}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={this.defaultBackgroundColors}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ relative={true}
+ scriptContext={this}
+ />
</div>;
}
- @computed get mainContent() {
- const sidebar = this.userDoc?.["tabs-panelContainer"];
- const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
- const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
- return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
- <div className="mainView-mainContent" style={{
- color: this.darkScheme ? "rgb(205,205,205)" : "black",
- //change to times 2 for both pinned
- height,
- width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%"
- }} >
- <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
- <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
- <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown}
- style={{ backgroundColor: this.defaultBackgroundColors(sidebar) }}>
- <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>
- <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.8vw"
- }}>
- {this.flyout}
- {this.expandButton}
+
+ @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, str: string) => {
+ this._lastButton && (this._lastButton.color = "white");
+ this._lastButton && (this._lastButton._backgroundColor = "");
+ if (this.panelContent === str && this.flyoutWidth !== 0) {
+ this.panelContent = "none";
+ this.flyoutWidth = 0;
+ } else {
+ let panelDoc: Doc | undefined;
+ switch (this.panelContent = str) {
+ 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 "Sharing": panelDoc = Doc.UserDoc()["sidebar-sharing"] as Doc ?? undefined; break;
+ case "UserDoc": panelDoc = Doc.UserDoc()["sidebar-userDoc"] as Doc ?? undefined; break;
+ }
+ 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>
- {this.dockingContent}
</div>
- </div>);
+ {this.dockingContent}
+ <MainViewNotifs />
+ {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>
+ </>;
+ }
+
+ @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>);
}
public static expandFlyout = action(() => {
MainView.Instance._flyoutTranslate = true;
MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250);
- MainView.Instance.sidebarButtonsDoc._columnWidth = MainView.Instance.flyoutWidth / 3 - 30;
+
});
@computed get expandButton() {
- return !this._flyoutTranslate ? (<div className="mainView-expandFlyoutButton" title="Re-attach sidebar" onPointerDown={MainView.expandFlyout}><FontAwesomeIcon icon="chevron-right" color="grey" size="lg" /></div>) : (null);
+ return !this._flyoutTranslate ? (<div className="mainView-expandFlyoutButton" title="Re-attach sidebar" onPointerDown={MainView.expandFlyout}></div>) : (null);
}
addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
@@ -524,7 +664,6 @@ export class MainView extends React.Component {
if (dockedBtns instanceof Doc) {
return <div className="mainView-docButtons" ref={this._docBtnRef}
style={{ height: !dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
- <MainViewNotifs />
<CollectionLinearView
Document={dockedBtns}
DataDoc={undefined}
@@ -532,6 +671,7 @@ export class MainView extends React.Component {
fieldKey={"data"}
dropAction={"alias"}
annotationsKey={""}
+ backgroundColor={this.defaultBackgroundColors}
rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
@@ -601,8 +741,40 @@ export class MainView extends React.Component {
</svg>;
}
+ @computed get search() {
+ return <div className="mainView-searchPanel">
+ {/* <div style={{ float: "left", marginLeft: "10px" }}>{Doc.CurrentUserEmail}</div> */}
+ <div><DocumentView Document={this.searchDoc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ onClick={undefined}
+ backgroundColor={this.defaultBackgroundColors}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ PanelWidth={this.getPWidth}
+ PanelHeight={this.getPHeight}
+ renderDepth={0}
+ focus={emptyFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ /></div>
+ </div>;
+ }
+
render() {
return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
+
{this.inkResources}
<DictationOverlay />
<SharingManager />
@@ -611,11 +783,12 @@ export class MainView extends React.Component {
<GoogleAuthenticationManager />
<HypothesisAuthenticationManager />
<DocumentDecorations />
+ {this.search}
<CollectionMenu />
<FormatShapePane />
- <RichTextMenu key="rich" />
+ <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
- {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (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}
linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 249715511..19387f619 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -10,7 +10,7 @@ export interface MainViewOverlayProps {
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
overlayDisplayedOpacity?: number;
- closeOnExternalClick?: () => void;
+ closeOnExternalClick?: () => void; // the close method of a MainViewModal, triggered if there is a click on the overlay (closing the modal)
}
@observer
@@ -21,7 +21,9 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
return !p.isDisplayed ? (null) : (
- <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}>
+ <div style={{
+ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none"
+ }}>
<div
className={"dialogue-box"}
style={{
diff --git a/src/client/views/MainViewNotifs.scss b/src/client/views/MainViewNotifs.scss
index 25ec95643..92d7d6ee3 100644
--- a/src/client/views/MainViewNotifs.scss
+++ b/src/client/views/MainViewNotifs.scss
@@ -1,5 +1,7 @@
.mainNotifs-container {
position:absolute;
+ z-index: 1000;
+ top: 12px;
.mainNotifs-badge {
position: absolute;
diff --git a/src/client/views/MainViewNotifs.tsx b/src/client/views/MainViewNotifs.tsx
index 05f890485..ce47e1cf1 100644
--- a/src/client/views/MainViewNotifs.tsx
+++ b/src/client/views/MainViewNotifs.tsx
@@ -3,28 +3,33 @@ import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { emptyFunction } from '../../Utils';
-import { SetupDrag } from '../util/DragManager';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { SetupDrag, DragManager } from '../util/DragManager';
import "./MainViewNotifs.scss";
-import { CollectionDockingView } from './collections/CollectionDockingView';
+import { MainView } from './MainView';
@observer
export class MainViewNotifs extends React.Component {
-
@observable static NotifsCol: Opt<Doc>;
- openNotifsCol = () => {
- if (MainViewNotifs.NotifsCol) {
- CollectionDockingView.AddRightSplit(MainViewNotifs.NotifsCol);
- }
+ _notifsRef = React.createRef<HTMLDivElement>();
+
+ onPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e,
+ (e: PointerEvent) => {
+ const dragData = new DragManager.DocumentDragData([MainViewNotifs.NotifsCol!]);
+ DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
+ return true;
+ },
+ returnFalse,
+ () => MainViewNotifs.NotifsCol && MainView.Instance.selectMenu(MainViewNotifs.NotifsCol, "Sharing"));
}
+
render() {
const length = MainViewNotifs.NotifsCol ? DocListCast(MainViewNotifs.NotifsCol.data).length : 0;
- const notifsRef = React.createRef<HTMLDivElement>();
- const dragNotifs = action(() => MainViewNotifs.NotifsCol!);
- return <div className="mainNotifs-container" ref={notifsRef}>
+ return <div className="mainNotifs-container" style={{ width: 15, height: 15 }} ref={this._notifsRef}>
<button className="mainNotifs-badge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
- onClick={this.openNotifsCol} onPointerDown={MainViewNotifs.NotifsCol ? SetupDrag(notifsRef, dragNotifs) : emptyFunction}>
+ onPointerDown={this.onPointerDown} >
{length}
</button>
</div>;
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index b4116e980..d7034fcfb 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -112,10 +112,10 @@ export class PreviewCursor extends React.Component<{}> {
} else if (e.clipboardData.items.length) {
const batch = UndoManager.StartBatch("collection view drop");
const files: File[] = [];
- for (let i = 0; i < e.clipboardData.items.length; i++) {
- const file = e.clipboardData.items[i].getAsFile();
+ Array.from(e.clipboardData.items).forEach(item => {
+ const file = item.getAsFile();
file && files.push(file);
- }
+ });
const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
generatedDocuments.forEach(PreviewCursor._addDocument);
batch.end();
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
new file mode 100644
index 000000000..6199d34d0
--- /dev/null
+++ b/src/client/views/PropertiesButtons.scss
@@ -0,0 +1,141 @@
+@import "globalCssVariables";
+
+$linkGap : 3px;
+
+.propertiesButtons-linkFlyout {
+ grid-column: 2/4;
+}
+
+.propertiesButtons-linkButton-empty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.propertiesButtons-linkButton-nonempty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.propertiesButtons-linkButton-empty,
+.propertiesButtons-linkButton-nonempty {
+ height: 30px;
+ width: 32px;
+ border-radius: 6px;
+ pointer-events: auto;
+ background-color: #121721;
+ color: #fcfbf7;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 10px;
+ margin-left: 3.5px;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+}
+
+.propertiesButtons {
+ margin-top: 3px;
+ grid-column: 1/4;
+ width: 100%;
+ height: auto;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+}
+
+.onClickFlyout-editScript {
+ text-align: center;
+ border: 0.5px solid grey;
+ background-color: rgb(230, 230, 230);
+ border-radius: 9px;
+ padding: 4px;
+}
+
+
+.propertiesButtons-button {
+ pointer-events: auto;
+ padding-right: 5px;
+ width: 25px;
+ border-radius: 5px;
+ margin-right: 22px;
+ margin-bottom: 8px;
+}
+
+.propertiesButtons-title {
+ background: #121721;
+ color: white;
+ font-size: 6px;
+ width: 40px;
+ padding: 3px;
+ height: 13px;
+ border-radius: 7px;
+ text-transform: uppercase;
+ text-align: center;
+ margin-top: -4px;
+}
+
+.propertiesButtons-linker {
+ height: 30px;
+ width: 32px;
+ text-align: center;
+ border-radius: 6px;
+ pointer-events: auto;
+ background-color: #121721;
+ color: #fcfbf7;
+ transition: 0.2s ease all;
+ margin-right: 5px;
+ padding-top: 5px;
+ margin-left: 3.5px;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+}
+
+.propertiesButtons-linker:hover {
+ cursor: pointer;
+ transform: scale(1.05);
+}
+
+
+@-moz-keyframes spin {
+ 100% {
+ -moz-transform: rotate(360deg);
+ }
+}
+
+@-webkit-keyframes spin {
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
+}
+
+@keyframes spin {
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes shadow-pulse {
+ 0% {
+ box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
+ }
+
+ 100% {
+ box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
new file mode 100644
index 000000000..5c584d270
--- /dev/null
+++ b/src/client/views/PropertiesButtons.tsx
@@ -0,0 +1,763 @@
+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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../fields/Doc";
+import { RichTextField } from '../../fields/RichTextField';
+import { Cast, NumCast, BoolCast } from "../../fields/Types";
+import { emptyFunction, setupMoveUpEvents, Utils } from "../../Utils";
+import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
+import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+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 { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
+import { TemplateMenu } from "./TemplateMenu";
+import { Template, Templates } from "./Templates";
+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';
+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";
+
+enum UtilityButtonState {
+ Default,
+ OpenRight,
+ OpenExternally
+}
+
+@observer
+export class PropertiesButtons extends React.Component<{}, {}> {
+ private _dragRef = React.createRef<HTMLDivElement>();
+ private _pullAnimating = false;
+ private _pushAnimating = false;
+ private _pullColorAnimating = false;
+
+ @observable private pushIcon: IconProp = "arrow-alt-circle-up";
+ @observable private pullIcon: IconProp = "arrow-alt-circle-down";
+ @observable private pullColor: string = "white";
+ @observable public isAnimatingFetch = false;
+ @observable public isAnimatingPulse = false;
+
+ @observable private openHover: UtilityButtonState = UtilityButtonState.Default;
+
+ @observable public static Instance: PropertiesButtons;
+ public static hasPushedHack = false;
+ public static hasPulledHack = false;
+
+
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } 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"; }
+
+ public startPullOutcome = action((success: boolean) => {
+ if (!this._pullAnimating) {
+ this._pullAnimating = true;
+ this.pullIcon = success ? "check-circle" : "stop-circle";
+ setTimeout(() => runInAction(() => {
+ this.pullIcon = "arrow-alt-circle-down";
+ this._pullAnimating = false;
+ }), 1000);
+ }
+ });
+
+ public startPushOutcome = action((success: boolean) => {
+ this.isAnimatingPulse = false;
+ if (!this._pushAnimating) {
+ this._pushAnimating = true;
+ this.pushIcon = success ? "check-circle" : "stop-circle";
+ setTimeout(() => runInAction(() => {
+ this.pushIcon = "arrow-alt-circle-up";
+ this._pushAnimating = false;
+ }), 1000);
+ }
+ });
+
+ public setPullState = action((unchanged: boolean) => {
+ this.isAnimatingFetch = false;
+ if (!this._pullColorAnimating) {
+ this._pullColorAnimating = true;
+ this.pullColor = unchanged ? "lawngreen" : "red";
+ setTimeout(this.clearPullColor, 1000);
+ }
+ });
+
+ private clearPullColor = action(() => {
+ this.pullColor = "white";
+ this._pullColorAnimating = false;
+ });
+
+ @computed
+ get considerGoogleDocsPush() {
+ const targetDoc = this.selectedDoc;
+ const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
+ const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div>} placement="top">
+ <div>
+ <div
+ className="propertiesButtons-linker"
+ style={{ animation }}
+ onClick={async () => {
+ await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
+ !published && runInAction(() => this.isAnimatingPulse = true);
+ PropertiesButtons.hasPushedHack = false;
+ targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "lg" : "sm"} />
+ </div>
+ <div className="propertiesButtons-title">Google</div>
+ </div>
+ </Tooltip>;
+ }
+
+ @computed
+ get considerGoogleDocsPull() {
+ const targetDoc = this.selectedDoc;
+ const dataDoc = targetDoc && Doc.GetProto(targetDoc);
+ const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
+
+ const title = (() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default: return `${!dataDoc?.unchanged ? "Pull from" : "Fetch"} Google Docs`;
+ case UtilityButtonState.OpenRight: return "Open in Right Split";
+ case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
+ }
+ })();
+
+ return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
+ title={<><div className="dash-tooltip">{title}</div></>} placement="top">
+ <div>
+ <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;
+ }
+ })}
+ onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
+ onClick={async e => {
+ const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
+ if (e.shiftKey) {
+ e.preventDefault();
+ let googleDoc = await Cast(dataDoc.googleDoc, Doc);
+ if (!googleDoc) {
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
+ googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
+ dataDoc.googleDoc = googleDoc;
+ }
+ CollectionDockingView.AddRightSplit(googleDoc);
+ } else if (e.altKey) {
+ e.preventDefault();
+ window.open(googleDocUrl);
+ } else {
+ this.clearPullColor();
+ PropertiesButtons.hasPulledHack = false;
+ targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
+ }
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="lg"
+ color="black"
+ style={{ WebkitAnimation: animation, MozAnimation: animation }}
+ icon={(() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
+ case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
+ case UtilityButtonState.OpenExternally: return "share";
+ }
+ })()}
+ />
+ </div>
+ <div className="propertiesButtons-title" style={{ backgroundColor: "white", color: "black" }}>Fetch</div>
+ </div>
+ </Tooltip>;
+ }
+ @computed
+ get pinButton() {
+ 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">
+ <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>
+
+ <div className="propertiesButtons-title" style={{
+ backgroundColor: Doc.isDocPinned(targetDoc) ? "white" : "black",
+ color: Doc.isDocPinned(targetDoc) ? "black" : "white"
+ }}
+ >{Doc.isDocPinned(targetDoc) ? "Unpin" : "Pin"}</div>
+ </div>
+ </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 with this view"}</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, -20px)', color: 'rgba(250,250,250,0.5)' }}>V</div>
+ </div>
+
+ <div className="propertiesButtons-title">{"View"}</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);
+ }
+ }
+
+ @computed
+ get copyButton() {
+ const targetDoc = this.selectedDoc;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Tap or Drag to create an alias"}</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>
+ <div className="propertiesButtons-title">Alias</div>
+ </div>
+ </Tooltip>;
+ }
+
+ @action @undoBatch
+ onLock = () => {
+ this.selectedDocumentView?.toggleLockPosition();
+ }
+
+ @computed
+ get lockButton() {
+ const targetDoc = this.selectedDoc;
+ return !targetDoc ? (null) : <Tooltip
+ title={<><div className="dash-tooltip">{this.selectedDoc?.lockedPosition ?
+ "Unlock Position" : "Lock Position"}</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>
+ <div className="propertiesButtons-title" style={{
+ backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "black",
+ color: BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white"
+ }}>Position </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>;
+ }
+
+ @computed
+ get deleteButton() {
+ const targetDoc = this.selectedDoc;
+ return !targetDoc ? (null) : <Tooltip
+ title={<><div className="dash-tooltip">{"Delete Document"}</div></>} placement="top">
+ <div>
+ <div className={"propertiesButtons-linkButton-empty"}
+ onPointerDown={this.deleteDocument}>
+ {<FontAwesomeIcon className="propertiesButtons-icon"
+ icon="trash-alt" size="lg" />}
+ </div>
+ <div className="propertiesButtons-title"> delete </div>
+ </div>
+ </Tooltip>;
+ }
+
+ @undoBatch
+ @action
+ deleteDocument = () => {
+ this.selectedDocumentView?.props.ContainingCollectionView?.removeDocument(this.selectedDocumentView?.props.Document);
+ }
+
+ @computed
+ get sharingButton() {
+ const targetDoc = this.selectedDoc;
+ return !targetDoc ? (null) : <Tooltip
+ title={<><div className="dash-tooltip">{"Share Document"}</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>
+ <div className="propertiesButtons-title"> share </div>
+ </div>
+ </Tooltip>;
+ }
+
+ @computed
+ get onClickButton() {
+ if (this.selectedDoc) {
+ return <Tooltip title={<><div className="dash-tooltip">Choose onClick behavior</div></>} placement="top">
+ <div>
+ <div className="propertiesButtons-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={this.onClickFlyout}>
+ <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />}
+ </div>
+ </Flyout>
+ </div>
+ <div className="propertiesButtons-title"> onclick </div>
+ </div>
+ </Tooltip>;
+ } else {
+ return null;
+ }
+ }
+
+ @undoBatch
+ @action
+ 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);
+ }
+ }
+
+ @undoBatch @action
+ editOnClickScript = () => {
+ if (this.selectedDoc) {
+ DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick");
+ }
+ }
+
+ @computed
+ get onClickFlyout() {
+ return <div><form>
+ <div className="radio">
+ <label>
+ <input type="radio" value="nothing"
+ checked={this.onClick === 'nothing'}
+ onChange={this.handleOptionChange} />
+ Select Document
+ </label>
+ </div>
+ <div className="radio">
+ <label>
+ <input type="radio" value="enterPortal"
+ checked={this.onClick === 'enterPortal'}
+ onChange={this.handleOptionChange} />
+ Enter Portal
+ </label>
+ </div>
+ <div className="radio">
+ <label>
+ <input type="radio" value="toggleDetail"
+ checked={this.onClick === 'toggleDetail'}
+ onChange={this.handleOptionChange} />
+ Toggle Detail
+ </label>
+ </div>
+ <div className="radio">
+ <label>
+ <input type="radio" value="linkInPlace"
+ checked={this.onClick === 'linkInPlace'}
+ onChange={this.handleOptionChange} />
+ Follow Link
+ </label>
+ </div>
+ <div className="radio">
+ <label>
+ <input type="radio" value="linkOnRight"
+ checked={this.onClick === 'linkOnRight'}
+ onChange={this.handleOptionChange} />
+ Open Link on Right
+ </label>
+ </div>
+ </form>
+ <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>
+ </div>;
+ }
+
+ @computed
+ get googlePhotosButton() {
+ const targetDoc = this.selectedDoc;
+ return !targetDoc ? (null) : <Tooltip
+ 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" />}
+ </div>
+ <div className="propertiesButtons-title"> google </div>
+ </div>
+ </Tooltip>;
+ }
+
+ @computed
+ 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">
+ <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>
+ <div className="propertiesButtons-title" style={{
+ backgroundColor: this.selectedDoc?.useClusters ? "white" : "black",
+ color: this.selectedDoc?.useClusters ? "black" : "white"
+ }}> clusters </div>
+ </div>
+ </Tooltip>;
+ }
+
+ @action @undoBatch
+ changeFitToBox = () => {
+ this.selectedDoc && (this.selectedDoc._fitToBox = !this.selectedDoc._fitToBox);
+ }
+
+ @action @undoBatch
+ changeClusters = () => {
+ this.selectedDoc && (this.selectedDoc.useClusters = !this.selectedDoc.useClusters);
+ }
+
+ @computed
+ get fitContentButton() {
+ const targetDoc = this.selectedDoc;
+ 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>
+ <div className="propertiesButtons-title" style={{
+ backgroundColor: this.selectedDoc?._fitToBox ? "white" : "black",
+ color: this.selectedDoc?._fitToBox ? "black" : "white"
+ }}> {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div>
+ </div>
+ </Tooltip>;
+ }
+
+ @undoBatch
+ @action
+ private makeMask = () => {
+ if (this.selectedDoc) {
+ this.selectedDoc._backgroundColor = "rgba(0,0,0,0.7)";
+ this.selectedDoc.mixBlendMode = "hard-light";
+ this.selectedDoc.color = "#9b9b9bff";
+ this.selectedDoc.stayInCollection = true;
+ this.selectedDoc.isInkMask = true;
+ }
+ }
+
+ @computed
+ get maskButton() {
+ const targetDoc = this.selectedDoc;
+ 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>
+ <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 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;
+
+ return <div><div className="propertiesButtons" style={{ paddingBottom: "5.5px" }}>
+ <div className="propertiesButtons-button">
+ {this.templateButton}
+ </div>
+ {/* <div className="propertiesButtons-button">
+ {this.metadataButton}
+ </div> */}
+ <div className="propertiesButtons-button">
+ {this.pinButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.pinWithViewButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.copyButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.lockButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.downloadButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.deleteButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.onClickButton}
+ </div>
+ <div className="propertiesButtons-button">
+ {this.sharingButton}
+ </div>
+ <div className="propertiesButtons-button" style={{ display: !considerPush ? "none" : "" }}>
+ {this.considerGoogleDocsPush}
+ </div>
+ <div className="propertiesButtons-button" style={{ display: !considerPull ? "none" : "" }}>
+ {this.considerGoogleDocsPull}
+ </div>
+ <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 className="propertiesButtons-button">
+ {this.contextButton}
+ </div>
+ </div>
+ </div>;
+ }
+}
diff --git a/src/client/views/RecommendationsBox.scss b/src/client/views/RecommendationsBox.scss
deleted file mode 100644
index 7d89042a4..000000000
--- a/src/client/views/RecommendationsBox.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-@import "globalCssVariables";
-
-.rec-content *{
- display: inline-block;
- margin: auto;
- width: 50;
- height: 150px;
- border: 1px dashed grey;
- padding: 10px 10px;
-}
-
-.rec-content {
- float: left;
- width: inherit;
- align-content: center;
-}
-
-.rec-scroll {
- overflow-y: scroll;
- overflow-x: hidden;
- position: absolute;
- pointer-events: all;
- // display: flex;
- z-index: 10000;
- box-shadow: gray 0.2vw 0.2vw 0.4vw;
- // flex-direction: column;
- background: whitesmoke;
- padding-bottom: 10px;
- padding-top: 20px;
- // border-radius: 15px;
- border: solid #BBBBBBBB 1px;
- width: 100%;
- text-align: center;
- // max-height: 250px;
- height: 100%;
- text-transform: uppercase;
- color: grey;
- letter-spacing: 2px;
-}
-
-.content {
- padding: 10px;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
-}
-
-.image-background {
- pointer-events: none;
- background-color: transparent;
- width: 50%;
- text-align: center;
- margin-left: 5px;
-}
-
-// bcz: UGH!! Can't have global settings like this!!!
-// img{
-// width: 100%;
-// height: 100%;
-// }
-
-.score {
- // margin-left: 15px;
- width: 50%;
- height: 100%;
- text-align: center;
- margin-left: 10px;
-}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
deleted file mode 100644
index 196151e32..000000000
--- a/src/client/views/RecommendationsBox.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { observable, action, computed, runInAction } from "mobx";
-import Measure from "react-measure";
-import "./RecommendationsBox.scss";
-import { Doc, DocListCast, WidthSym, HeightSym } from "../../fields/Doc";
-import { DocumentIcon } from "./nodes/DocumentIcon";
-import { StrCast, NumCast } from "../../fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero, returnEmptyFilter } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { ObjectField } from "../../fields/ObjectField";
-import { DocumentView } from "./nodes/DocumentView";
-import { DocumentType } from '../documents/DocumentTypes';
-import { ClientRecommender } from "../ClientRecommender";
-import { DocServer } from "../DocServer";
-import { Id } from "../../fields/FieldSymbols";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { DocumentManager } from "../util/DocumentManager";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons";
-import { DocUtils } from "../documents/Documents";
-
-export interface RecProps {
- documents: { preview: Doc, similarity: number }[];
- node: Doc;
-}
-
-library.add(faBullseye, faLink);
-
-@observer
-export class RecommendationsBox extends React.Component<FieldViewProps> {
-
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); }
-
- // @observable private _display: boolean = false;
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable private _width: number = 0;
- @observable private _height: number = 0;
- @observable.shallow private _docViews: JSX.Element[] = [];
- // @observable private _documents: { preview: Doc, score: number }[] = [];
- private previewDocs: Doc[] = [];
-
- constructor(props: FieldViewProps) {
- super(props);
- }
-
- @action
- private DocumentIcon(doc: Doc) {
- const layoutresult = StrCast(doc.type);
- let renderDoc = doc;
- //let box: number[] = [];
- if (layoutresult.indexOf(DocumentType.COL) !== -1) {
- renderDoc = Doc.MakeDelegate(renderDoc);
- }
- const returnXDimension = () => 150;
- const returnYDimension = () => 150;
- const scale = () => returnXDimension() / NumCast(renderDoc._nativeWidth, returnXDimension());
- //let scale = () => 1;
- const newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
- newRenderDoc.height = NumCast(this.props.Document.documentIconHeight);
- newRenderDoc.autoHeight = false;
- const docview = <div>
- <DocumentView
- fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
- Document={newRenderDoc}
- addDocument={returnFalse}
- LibraryPath={emptyPath}
- removeDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- renderDepth={1}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={returnXDimension}
- PanelHeight={returnYDimension}
- focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnFalse}
- whenActiveChanged={returnFalse}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ContentScaling={scale}
- />
- </div>;
- return docview;
-
- }
-
- // @action
- // closeMenu = () => {
- // this._display = false;
- // this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id]));
- // this.previewDocs = [];
- // }
-
- // @action
- // resetDocuments = () => {
- // this._documents = [];
- // }
-
- // @action
- // displayRecommendations(x: number, y: number) {
- // this._pageX = x;
- // this._pageY = y;
- // this._display = true;
- // }
-
- static readonly buffer = 20;
-
- // get pageX() {
- // const x = this._pageX;
- // if (x < 0) {
- // return 0;
- // }
- // const width = this._width;
- // if (x + width > window.innerWidth - RecommendationsBox.buffer) {
- // return window.innerWidth - RecommendationsBox.buffer - width;
- // }
- // return x;
- // }
-
- // get pageY() {
- // const y = this._pageY;
- // if (y < 0) {
- // return 0;
- // }
- // const height = this._height;
- // if (y + height > window.innerHeight - RecommendationsBox.buffer) {
- // return window.innerHeight - RecommendationsBox.buffer - height;
- // }
- // return y;
- // }
-
- // get createDocViews() {
- // return DocListCast(this.props.Document.data).map(doc => {
- // return (
- // <div className="content">
- // <span style={{ height: NumCast(this.props.Document.documentIconHeight) }} className="image-background">
- // {this.DocumentIcon(doc)}
- // </span>
- // <span className="score">{NumCast(doc.score).toFixed(4)}</span>
- // <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
- // <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
- // </div>
- // <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}>
- // <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
- // </div>
- // </div>
- // );
- // });
- // }
-
- componentDidMount() { //TODO: invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set
- runInAction(() => {
- if (this._docViews.length === 0) {
- this._docViews = DocListCast(this.props.Document.data).map(doc => {
- return (
- <div className="content">
- <span style={{ height: NumCast(this.props.Document.documentIconHeight) }} className="image-background">
- {this.DocumentIcon(doc)}
- </span>
- <span className="score">{NumCast(doc.score).toFixed(4)}</span>
- <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
- </div>
- <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", "", undefined)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
- </div>
- </div>
- );
- });
- }
- });
- }
-
- render() { //TODO: Invariant violation: max depth exceeded error. Occurs when images are rendered.
- // if (!this._display) {
- // return null;
- // }
- // let style = { left: this.pageX, top: this.pageY };
- //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px"
- let title = StrCast((this.props.Document.sourceDoc as Doc).title);
- if (title.length > 15) {
- title = title.substring(0, 15) + "...";
- }
- return (
- <div className="rec-scroll">
- <p>Recommendations for "{title}"</p>
- {this._docViews}
- </div>
- );
- }
- //
- //
-} \ No newline at end of file
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
deleted file mode 100644
index 084f952a3..000000000
--- a/src/client/views/SearchDocBox.tsx
+++ /dev/null
@@ -1,428 +0,0 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-//import "./SearchBoxDoc.scss";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
-import { returnFalse, returnZero } from "../../Utils";
-import { Docs } from "../documents/Documents";
-import { SearchUtil } from "../util/SearchUtil";
-import { EditableView } from "./EditableView";
-import { ContentFittingDocumentView } from "./nodes/ContentFittingDocumentView";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { FilterBox } from "./search/FilterBox";
-import { SearchItem } from "./search/SearchItem";
-import React = require("react");
-
-export interface RecProps {
- documents: { preview: Doc, similarity: number }[];
- node: Doc;
-
-}
-
-library.add(faBullseye, faLink);
-export const keyPlaceholder = "Query";
-
-@observer
-export class SearchDocBox extends React.Component<FieldViewProps> {
-
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchDocBox, fieldKey); }
-
- // @observable private _display: boolean = false;
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable private _width: number = 0;
- @observable private _height: number = 0;
- @observable.shallow private _docViews: JSX.Element[] = [];
- // @observable private _documents: { preview: Doc, score: number }[] = [];
- private previewDocs: Doc[] = [];
-
- constructor(props: FieldViewProps) {
- super(props);
- this.editingMetadata = this.editingMetadata || false;
- //SearchBox.Instance = this;
- this.resultsScrolled = this.resultsScrolled.bind(this);
- }
-
-
- @computed
- private get editingMetadata() {
- return BoolCast(this.props.Document.editingMetadata);
- }
-
- private set editingMetadata(value: boolean) {
- this.props.Document.editingMetadata = value;
- }
-
- static readonly buffer = 20;
-
- componentDidMount() {
- runInAction(() => {
- this.query = StrCast(this.props.Document.searchText);
- this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }));
-
- });
- if (this.inputRef.current) {
- this.inputRef.current.focus();
- runInAction(() => {
- this._searchbarOpen = true;
- });
- }
- }
-
- @observable
- private content: Doc | undefined;
-
- @action
- updateKey = async (newKey: string) => {
- this.query = newKey;
- if (newKey.length > 1) {
- const newdocs = await this.getAllResults(this.query);
- const things = newdocs.docs;
- runInAction(() => {
- this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query });
- });
- }
-
-
- //this.keyRef.current && this.keyRef.current.setIsFocused(false);
- //this.query.length === 0 && (this.query = keyPlaceholder);
- return true;
- }
-
- @computed
- public get query() {
- return StrCast(this.props.Document.query);
- }
-
- public set query(value: string) {
- this.props.Document.query = value;
- }
-
- @observable private _searchString: string = "";
- @observable private _resultsOpen: boolean = false;
- @observable private _searchbarOpen: boolean = false;
- @observable private _results: [Doc, string[], string[]][] = [];
- private _resultsSet = new Map<Doc, number>();
- @observable private _openNoResults: boolean = false;
- @observable private _visibleElements: JSX.Element[] = [];
-
- private resultsRef = React.createRef<HTMLDivElement>();
- public inputRef = React.createRef<HTMLInputElement>();
-
- private _isSearch: ("search" | "placeholder" | undefined)[] = [];
- private _numTotalResults = -1;
- private _endIndex = -1;
-
-
- private _maxSearchIndex: number = 0;
- private _curRequest?: Promise<any> = undefined;
-
- @action
- getViews = async (doc: Doc) => {
- const results = await SearchUtil.GetViewsOfDocument(doc);
- let toReturn: Doc[] = [];
- await runInAction(() => {
- toReturn = results;
- });
- return toReturn;
- }
-
- @action.bound
- onChange(e: React.ChangeEvent<HTMLInputElement>) {
- this._searchString = e.target.value;
-
- this._openNoResults = false;
- this._results = [];
- this._resultsSet.clear();
- this._visibleElements = [];
- this._numTotalResults = -1;
- this._endIndex = -1;
- this._curRequest = undefined;
- this._maxSearchIndex = 0;
- }
-
- enter = async (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
- const newdocs = await this.getAllResults(this.query);
- this.content = Docs.Create.TreeDocument(newdocs.docs, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` });
- }
- }
-
-
- @action
- submitSearch = async () => {
- let query = this._searchString;
- query = FilterBox.Instance.getFinalQuery(query);
- this._results = [];
- this._resultsSet.clear();
- this._isSearch = [];
- this._visibleElements = [];
- FilterBox.Instance.closeFilter();
-
- //if there is no query there should be no result
- if (query === "") {
- return;
- }
- else {
- this._endIndex = 12;
- this._maxSearchIndex = 0;
- this._numTotalResults = -1;
- await this.getResults(query);
- }
-
- runInAction(() => {
- this._resultsOpen = true;
- this._searchbarOpen = true;
- this._openNoResults = true;
- this.resultsScrolled();
- });
- }
-
- getAllResults = async (query: string) => {
- return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 });
- }
-
- private get filterQuery() {
- const types = FilterBox.Instance.filterTypes;
- const includeDeleted = FilterBox.Instance.getDataStatus();
- return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : "");
- }
-
-
- private NumResults = 25;
- private lockPromise?: Promise<void>;
- getResults = async (query: string) => {
- if (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) => {
-
- // happens at the beginning
- if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
- this._numTotalResults = res.numFound;
- }
-
- const highlighting = res.highlighting || {};
- 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 highlights: typeof res.highlighting = {};
- docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]);
- const filteredDocs = FilterBox.Instance.filterDocsByType(docs);
- runInAction(() => {
- // this._results.push(...filteredDocs);
- filteredDocs.forEach(doc => {
- 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)) : [];
- 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;
-
- await this._curRequest;
- }
- this.resultsScrolled();
- res();
- });
- return this.lockPromise;
- }
-
- collectionRef = React.createRef<HTMLSpanElement>();
- startDragCollection = async () => {
- const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString));
- const filtered = FilterBox.Instance.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);
- }
- });
- let x = 0;
- let y = 0;
- for (const doc of docs.map(d => Doc.Layout(d))) {
- doc.x = x;
- doc.y = y;
- const size = 200;
- const aspect = NumCast(doc._nativeHeight) / NumCast(doc._nativeWidth, 1);
- if (aspect > 1) {
- doc._height = size;
- doc._width = size / aspect;
- } else if (aspect > 0) {
- doc._width = size;
- doc._height = size * aspect;
- } else {
- doc._width = size;
- doc._height = size;
- }
- x += 250;
- if (x > 1000) {
- x = 0;
- y += 300;
- }
- }
- //return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
- return Docs.Create.QueryDocument({ _width: 200, _height: 400, searchText: this._searchString, title: `Query Docs: "${this._searchString}"` });
- }
-
- @action.bound
- openSearch(e: React.SyntheticEvent) {
- e.stopPropagation();
- this._openNoResults = false;
- FilterBox.Instance.closeFilter();
- this._resultsOpen = true;
- this._searchbarOpen = true;
- FilterBox.Instance._pointerTime = e.timeStamp;
- }
-
- @action.bound
- closeSearch = () => {
- FilterBox.Instance.closeFilter();
- this.closeResults();
- this._searchbarOpen = false;
- }
-
- @action.bound
- closeResults() {
- this._resultsOpen = false;
- this._results = [];
- this._resultsSet.clear();
- this._visibleElements = [];
- this._numTotalResults = -1;
- this._endIndex = -1;
- this._curRequest = undefined;
- }
-
- @action
- resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
- if (!this.resultsRef.current) return;
- const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0;
- const itemHght = 53;
- const startIndex = Math.floor(Math.max(0, scrollY / itemHght));
- const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght)));
-
- this._endIndex = endIndex === -1 ? 12 : endIndex;
-
- if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) {
- this._visibleElements = [<div className="no-result">No Search Results</div>];
- return;
- }
-
- if (this._numTotalResults <= this._maxSearchIndex) {
- this._numTotalResults = this._results.length;
- }
-
- // only hit right at the beginning
- // visibleElements is all of the elements (even the ones you can't see)
- else if (this._visibleElements.length !== this._numTotalResults) {
- // undefined until a searchitem is put in there
- this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
- // indicates if things are placeholders
- this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
- }
-
- for (let i = 0; i < this._numTotalResults; 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 (i < startIndex || i > endIndex) {
- if (this._isSearch[i] !== "placeholder") {
- this._isSearch[i] = "placeholder";
- this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>;
- }
- }
- else {
- if (this._isSearch[i] !== "search") {
- let result: [Doc, string[], string[]] | undefined = undefined;
- if (i >= this._results.length) {
- this.getResults(this._searchString);
- if (i < this._results.length) result = this._results[i];
- if (result) {
- const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
- this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
- this._isSearch[i] = "search";
- }
- }
- else {
- result = this._results[i];
- if (result) {
- const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
- this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
- this._isSearch[i] = "search";
- }
- }
- }
- }
- }
- if (this._maxSearchIndex >= this._numTotalResults) {
- this._visibleElements.length = this._results.length;
- this._isSearch.length = this._results.length;
- }
- }
-
- @computed
- get resFull() { return this._numTotalResults <= 8; }
-
- @computed
- get resultHeight() { return this._numTotalResults * 70; }
-
- render() {
- const isEditing = this.editingMetadata;
- return !this.content ? (null) : (
- <div style={{ pointerEvents: "all" }}>
- <ContentFittingDocumentView {...this.props}
- Document={this.content}
- rootSelected={returnFalse}
- bringToFront={returnFalse}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- parentActive={this.props.active}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}>
- </ContentFittingDocumentView>
- <div
- style={{
- position: "absolute",
- right: 0,
- width: 20,
- height: 20,
- background: "black",
- pointerEvents: "all",
- opacity: 1,
- transition: "0.4s opacity ease",
- zIndex: 99,
- top: 0,
- }}
- title={"Add Metadata"}
- onClick={action(() => this.editingMetadata = !this.editingMetadata)}
- />
- <div className="editableclass" onKeyPress={this.enter} style={{ opacity: isEditing ? 1 : 0, pointerEvents: isEditing ? "auto" : "none", transition: "0.4s opacity ease", position: "absolute", top: 0, left: 0, height: 20, width: "-webkit-fill-available" }}>
- <EditableView
- contents={this.query}
- SetValue={this.updateKey}
- GetValue={() => ""}
- />
- </div>
- </div >
- );
- }
-
-} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 9fb8a227e..eb20fc257 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -63,14 +63,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
this.props.docViews.map(dv => dv.switchViews(false, "layout"));
}
- toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => {
- SelectionManager.DeselectAll();
- const topDocView = this.props.docViews[0];
- const ex = e.target.getBoundingClientRect().left;
- const ey = e.target.getBoundingClientRect().top;
- DocumentView.FloatDoc(topDocView, ex, ey);
- }
-
toggleAudio = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.props.docViews.map(dv => dv.props.Document._showAudio = e.target.checked);
}
@@ -127,7 +119,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
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={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />);
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));
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index c4cae7e8d..bb9e108cb 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -12,7 +12,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _touchDrag: boolean = false;
protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 1895c06a1..4204ef5bb 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -12,12 +12,90 @@
width: 100%;
height: 100%;
position: absolute;
+
.miniThumb {
background: #25252525;
position: absolute;
}
}
}
+
+.miniPres:hover {
+ opacity: 1;
+}
+
+.miniPres {
+ position: absolute;
+ overflow: hidden;
+ right: 10;
+ top: 10;
+ opacity: 0.1;
+ transition: all 0.4s;
+ /* 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-rows: 100%;
+ height: 100%;
+ justify-items: center;
+ align-items: center;
+
+ .miniPres-button-text {
+ display: flex;
+ height: 20;
+ font-weight: 400;
+ min-width: 100%;
+ border-radius: 5px;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ }
+
+ .miniPres-button-frame {
+ justify-self: center;
+ align-self: center;
+ align-items: center;
+ display: grid;
+ grid-template-columns: auto auto auto;
+ justify-content: space-around;
+ font-size: 11;
+ margin-left: 7;
+ width: 30;
+ height: 85%;
+ background-color: rgba(91, 157, 221, 0.4);
+ border-radius: 5px;
+ }
+
+ .miniPres-divider {
+ width: 0.5px;
+ height: 80%;
+ border-right: solid 2px #5a5a5a;
+ }
+
+ .miniPres-button {
+ display: flex;
+ height: 20;
+ min-width: 20;
+ border-radius: 100%;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ }
+
+ .miniPres-button:hover {
+ background-color: #5a5a5a;
+ }
+
+ .miniPres-button-text:hover {
+ background-color: #5a5a5a;
+ }
+ }
+}
+
+
.lm_title {
margin-top: 3px;
border-radius: 5px;
@@ -27,6 +105,7 @@
transform: translate(0px, -3px);
cursor: grab;
}
+
.lm_title.focus-visible {
cursor: text;
}
@@ -34,23 +113,39 @@
.lm_title_wrap {
overflow: hidden;
height: 19px;
- margin-top: -3px;
- display:inline-block;
+ margin-top: -2px;
+ display: inline-block;
}
+
.lm_active .lm_title {
border: solid 1px lightgray;
}
+
.lm_header .lm_tab .lm_close_tab {
position: absolute;
text-align: center;
}
.lm_header .lm_tab {
- padding-right : 20px;
+ padding-right: 20px;
+ margin-top: -1px;
+ border-bottom: 1px black;
+ .collectionDockingView-gear {
+ display: none;
+ }
+}
+
+.lm_header .lm_tab.lm_active {
+ padding-right: 20px;
+ margin-top: 1px;
+ border-bottom: unset;
+ .collectionDockingView-gear {
+ display: inline-block;
+ }
}
.lm_popout {
- display:none;
+ display: none;
}
.messageCounter {
@@ -73,14 +168,15 @@
position: absolute;
top: 0;
left: 0;
+
// overflow: hidden; // bcz: menus don't show up when this is on (e.g., the parentSelectorMenu)
.collectionDockingView-gear {
padding-left: 5px;
height: 15px;
width: 18px;
- display: inline-block;
margin: auto;
}
+
.collectionDockingView-dragAsDocument {
touch-action: none;
position: absolute;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 53b2d5254..7e096fa37 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,9 +1,8 @@
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 } from "mobx";
+import { action, computed, Lambda, observable, reaction, runInAction, trace, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
-import Measure from "react-measure";
import * as GoldenLayout from "../../../client/goldenLayout";
import { DateField } from '../../../fields/DateField';
import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc";
@@ -31,6 +30,10 @@ 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';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -464,6 +467,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
this._flush = true;
}
+ if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
+ return;
+ } else {
+ e.stopPropagation();
+ }
}
updateDataField = async (json: string) => {
@@ -505,7 +513,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
if (doc instanceof Doc) {
- //tab.titleElement[0].outerHTML = `<input class='lm_title' style="background:black" value='${doc.title}' />`;
tab.titleElement[0].onclick = (e: any) => tab.titleElement[0].focus();
tab.titleElement[0].onchange = (e: any) => {
tab.titleElement[0].size = e.currentTarget.value.length + 1;
@@ -520,6 +527,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
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;
@@ -595,7 +606,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
stackCreated = (stack: any) => {
//stack.header.controlsContainer.find('.lm_popout').hide();
- stack.header.element[0].style.backgroundColor = DocServer.Control.isReadOnly() ? "#228540" : undefined;
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" }));
@@ -675,10 +685,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
@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));
@@ -739,9 +754,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
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, "white") }),
+ (data) => {
+ const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document));
+ 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);
}
@@ -750,6 +772,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
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.
}
}
@@ -769,10 +795,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
let scaling = 1;
if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
scaling = 1;
- } else if ((this.layoutDoc?._fitWidth) ||
- this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
+ } 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 {
+ } 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)) {
@@ -783,7 +809,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
// }
const wscale = this.panelWidth() / nativeW;
scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
- }
+ } else scaling = 1;
return scaling;
}
@@ -838,6 +864,31 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
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;
+ }
+ renderMiniPres() {
+ return (
+ <div className="miniPres"
+ style={{ width: 250, height: 30, background: '#323232' }}
+ >
+ {<div className="miniPresOverlay">
+ <div className="miniPres-button" onClick={PresBox.Instance.back}><FontAwesomeIcon icon={"arrow-left"} /></div>
+ <div className="miniPres-button" onClick={() => PresBox.Instance.startAutoPres(PresBox.Instance.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div>
+ <div className="miniPres-button" onClick={PresBox.Instance.next}><FontAwesomeIcon icon={"arrow-right"} /></div>
+ <div className="miniPres-divider"></div>
+ <div className="miniPres-button-text">
+ Slide {PresBox.Instance.itemIndex + 1} / {PresBox.Instance.childDocs.length}
+ {PresBox.Instance.playButtonFrames}
+ </div>
+ <div className="miniPres-divider"></div>
+ <div className="miniPres-button-text" onClick={PresBox.Instance.updateMinimize}>EXIT</div>
+ </div>}
+ </div>
+ );
+ }
renderMiniMap() {
return <div className="miniMap" style={{
width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
@@ -920,6 +971,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
{document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+ {document._viewType === CollectionViewType.Freeform && this._document?.miniPres ? this.renderMiniPres() : (null)}
</>;
}
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index b8b72e756..f5c4299a9 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -2,7 +2,7 @@
@import "../_nodeModuleOverrides";
.collectionLinearView-outer {
- overflow: hidden;
+ overflow: visible;
height: 100%;
.collectionLinearView {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 407524353..a9e812ad3 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -161,7 +161,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
- backgroundColor={returnEmptyString}
+ backgroundColor={this.props.backgroundColor}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -176,7 +176,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.StartLink.title}
+ Creating link from: {DocumentLinksButton.StartLink.props.Document.title}
</span>
<Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 9a7ea2c93..c772dcfe7 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -238,7 +238,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
contents: "+ NEW",
HeadingObject: this.props.headingObject,
toggle: this.toggleVisibility,
- color: this.color
};
const showChrome = (chromeStatus !== 'view-mode' && chromeStatus !== 'disabled');
const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
@@ -278,7 +277,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
oneLine: true,
HeadingObject: this.props.headingObject,
toggle: this.toggleVisibility,
- color: this.color
};
return this.props.parent.props.Document.miniHeaders ?
<div className="collectionStackingView-miniHeader">
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 9da204787..b41cbe92d 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -2,8 +2,8 @@
.collectionMenu-cont {
- position:relative;
- display:inline-flex;
+ position: relative;
+ display: inline-flex;
width: 100%;
opacity: 0.9;
z-index: 9001;
@@ -12,14 +12,15 @@
color: white;
transform-origin: top left;
top: 0;
- width:100%;
+ width: 100%;
.antimodeMenu-button {
padding: 0;
width: 30px;
display: flex;
+
svg {
- margin:auto;
+ margin: auto;
}
}
@@ -108,6 +109,7 @@
margin-top: auto;
margin-bottom: auto;
}
+
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
@@ -318,12 +320,14 @@
text-align: center;
display: block;
}
+
.color-previewI {
width: 80%;
height: 20%;
bottom: 0;
position: absolute;
}
+
.color-previewII {
width: 80%;
height: 80%;
@@ -336,7 +340,7 @@
margin: auto;
/* Make the buttons appear below each other */
}
-
+
.btn-draw {
display: inline-flex;
margin: auto;
@@ -364,6 +368,7 @@
}
}
+
.numKeyframe {
flex-direction: column;
padding-top: 5px;
@@ -374,6 +379,7 @@
display: block;
margin: auto;
}
+
border-right: solid gray 1px;
}
}
@@ -398,14 +404,14 @@
.collectionSchemaViewChrome-toggler {
width: 100px;
- height: 41px;
+ height: 35px;
background-color: black;
position: relative;
}
.collectionSchemaViewChrome-togglerButton {
width: 47px;
- height: 35px;
+ height: 30px;
background-color: $light-color-secondary;
// position: absolute;
transition: all 0.5s ease;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 24be69050..53d2a136e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,45 +1,62 @@
import React = require("react");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
+import { Tooltip } from "@material-ui/core";
+import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc";
-import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
-import AntimodeMenu from "../AntimodeMenu";
-import "./CollectionMenu.scss";
-import { undoBatch } from "../../util/UndoManager";
-import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { List } from "../../../fields/List";
-import { EditableView } from "../EditableView";
+import { ColorState } from "react-color";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Document } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
-import { listSpec } from "../../../fields/Schema";
-import FormatShapePane from "./collectionFreeForm/FormatShapePane";
-import { ActiveFillColor, SetActiveInkWidth, ActiveInkColor, SetActiveBezierApprox, SetActiveArrowEnd, SetActiveArrowStart, SetActiveFillColor, SetActiveInkColor } from "../InkingStroke";
-import GestureOverlay from "../GestureOverlay";
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 { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
-import { Document } from "../../../fields/documentSchemas";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DragManager } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
+import AntimodeMenu from "../AntimodeMenu";
+import { EditableView } from "../EditableView";
+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 { ColorState } from "react-color";
-import { ObjectField } from "../../../fields/ObjectField";
+import RichTextMenu from "../nodes/formattedText/RichTextMenu";
+import "./CollectionMenu.scss";
+import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
@observer
export default class CollectionMenu extends AntimodeMenu {
static Instance: CollectionMenu;
- @observable SelectedCollection: CollectionView | undefined;
+ @observable SelectedCollection: DocumentView | undefined;
+ @observable FieldKey: string;
constructor(props: Readonly<{}>) {
super(props);
+ this.FieldKey = "";
CollectionMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
this.jumpTo(300, 300);
}
+ componentDidMount() {
+ reaction(() => SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0],
+ (doc) => doc && this.SetSelection(doc));
+ }
+
+ @action
+ SetSelection(view: DocumentView) {
+ this.SelectedCollection = view;
+ }
+
@action
toggleMenuPin = (e: React.MouseEvent) => {
Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
@@ -48,20 +65,45 @@ export default class CollectionMenu extends AntimodeMenu {
}
}
+ @action
+ toggleProperties = () => {
+ if (CurrentUserUtils.propertiesWidth > 0) {
+ CurrentUserUtils.propertiesWidth = 0;
+ } else {
+ CurrentUserUtils.propertiesWidth = 250;
+ }
+ }
+
render() {
- const button = <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- </button>;
+ const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
+ <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+ </button>
+ </Tooltip>;
+
+ const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left";
+ const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel";
+
+ const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom">
+ <button className="antimodeMenu-button" key="properties" onPointerDown={this.toggleProperties}>
+ <FontAwesomeIcon icon={propIcon} size="lg" />
+ </button>
+ </Tooltip>;
return this.getElement(!this.SelectedCollection ? [button] :
- [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+ [<CollectionViewBaseChrome key="chrome"
+ docView={this.SelectedCollection}
+ fieldKey={Doc.LayoutFieldKey(this.SelectedCollection?.props.Document)}
+ type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
+ prop,
button]);
}
}
interface CollectionMenuProps {
- CollectionView: CollectionView;
type: CollectionViewType;
+ fieldKey: string;
+ docView: DocumentView;
}
const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@@ -70,7 +112,8 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
- get target() { return this.props.CollectionView.props.Document; }
+ get document() { return this.props.docView?.props.Document; }
+ get target() { return this.document; }
_templateCommand = {
params: ["target", "source"], title: "item view",
script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
@@ -84,11 +127,37 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
initialize: emptyFunction,
};
_contentCommand = {
- params: ["target", "source"], title: "clear content",
+ 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),
initialize: emptyFunction,
};
+ _onClickCommand = {
+ params: ["target", "proxy"], title: "copy onClick",
+ script: `{ if (self.proxy?.[0]) {
+ getProto(self.proxy[0]).onClick = copyField(self.target.onClick);
+ getProto(self.proxy[0]).target = self.target.target;
+ getProto(self.proxy[0]).source = copyField(self.target.source);
+ }}`,
+ immediate: undoBatch((source: Doc[]) => { }),
+ initialize: emptyFunction,
+ };
+ _openLinkInCommand = {
+ params: ["target", "container"], title: "link follow target",
+ script: `{ if (self.container?.length) {
+ getProto(self.target).linkContainer = self.container[0];
+ getProto(self.target).isLinkButton = true;
+ getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
+ }}`,
+ immediate: undoBatch((container: Doc[]) => {
+ if (container.length) {
+ Doc.GetProto(this.target).linkContainer = container[0];
+ Doc.GetProto(this.target).isLinkButton = true;
+ Doc.GetProto(this.target).onClick = ScriptField.MakeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
+ }
+ }),
+ initialize: emptyFunction,
+ };
_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'];",
@@ -114,23 +183,24 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; },
};
- _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
- _stacking_commands = [this._contentCommand, this._templateCommand];
- _masonry_commands = [this._contentCommand, this._templateCommand];
- _schema_commands = [this._templateCommand, this._narrativeCommand];
- _tree_commands = [];
+ @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand] : [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]; }
+ @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; }
+ @computed get _tree_commands() { return undefined; }
private get _buttonizableCommands() {
switch (this.props.type) {
+ default: return this._doc_commands;
+ case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Tree: return this._tree_commands;
case CollectionViewType.Schema: return this._schema_commands;
case CollectionViewType.Stacking: return this._stacking_commands;
case CollectionViewType.Masonry: return this._stacking_commands;
- case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Time: return this._freeform_commands;
case CollectionViewType.Carousel: return this._freeform_commands;
case CollectionViewType.Carousel3D: return this._freeform_commands;
}
- return [];
}
private _picker: any;
private _commandRef = React.createRef<HTMLInputElement>();
@@ -138,7 +208,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
@observable private _currentKey: string = "";
componentDidMount = action(() => {
- this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
+ this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : "");
});
@undoBatch
@@ -162,24 +232,35 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
this.document._facetWidth = 0;
}
-
@computed get subChrome() {
switch (this.props.type) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- default: return null;
+ default: return this.otherSubChrome;
+ case CollectionViewType.Invalid:
+ case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
+ case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Docking: return (<CollectionDockingChrome key="collchrome" {...this.props} />);
}
}
- private get document() {
- return this.props.CollectionView.props.Document;
+ @computed get otherSubChrome() {
+ const docType = this.props.docView.Document.type;
+ switch (docType) {
+ default: return (null);
+ case DocumentType.IMG: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
+ case DocumentType.PDF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
+ case DocumentType.INK: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
+ case DocumentType.WEB: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
+ case DocumentType.VID: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
+ case DocumentType.RTF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />);
+ }
}
+
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
@@ -193,7 +274,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
protected drop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
if (docDragData?.draggedDocuments.length) {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
+ this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
}
return true;
@@ -201,83 +282,114 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
dragViewDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
- const vtype = this.props.CollectionView.collectionViewType;
+ const vtype = this.props.type;
const c = {
params: ["target"], title: vtype,
- script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
- immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
+ script: `this.target._viewType = '${StrCast(this.props.type)}'`,
+ immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
- { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
+ { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
return true;
}, emptyFunction, emptyFunction);
}
dragCommandDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
+ this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c =>
DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
- { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+ { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
return true;
}, emptyFunction, () => {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
+ this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
});
}
@computed get templateChrome() {
return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
- <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <button className={"antimodeMenu-button"} >
- <FontAwesomeIcon icon="bullseye" size="lg" />
- </button>
- <select
- className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
- {this._buttonizableCommands.map(cmd =>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
- )}
- </select>
- </div>
+ <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
+ <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <button className={"antimodeMenu-button"} >
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
+ <select
+ className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
+ {this._buttonizableCommands?.map(cmd =>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+ )}
+ </select>
+ </div>
+ </Tooltip>
</div>;
}
@computed get viewModes() {
return <div className="collectionViewBaseChrome-viewModes" >
- <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
- <button className={"antimodeMenu-button"}>
- <FontAwesomeIcon icon="bullseye" size="lg" />
- </button>
- <select
- className="collectionViewBaseChrome-viewPicker"
- onPointerDown={stopPropagation}
- onChange={this.viewChanged}
- value={StrCast(this.props.CollectionView.props.Document._viewType)}>
- {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
- <option
- key={Utils.GenerateGuid()}
- className="collectionViewBaseChrome-viewOption"
- onPointerDown={stopPropagation}
- value={type}>
- {type[0].toUpperCase() + type.substring(1)}
- </option>
- ))}
- </select>
- </div>
+ <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
+ <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}>
+ <button className={"antimodeMenu-button"}>
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
+ <select
+ className="collectionViewBaseChrome-viewPicker"
+ onPointerDown={stopPropagation}
+ onChange={this.viewChanged}
+ value={StrCast(this.props.type)}>
+ {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
+ <option
+ key={Utils.GenerateGuid()}
+ className="collectionViewBaseChrome-viewOption"
+ onPointerDown={stopPropagation}
+ value={type}>
+ {type[0].toUpperCase() + type.substring(1)}
+ </option>
+ ))}
+ </select>
+ </div>
+ </Tooltip>
</div>;
}
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else { return undefined; }
+ }
+ @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get notACollection() {
+ if (this.selectedDoc) {
+ const layoutField = Doc.LayoutField(this.selectedDoc);
+ return this.props.type === CollectionViewType.Docking ||
+ typeof (layoutField) === "string" && !layoutField?.includes("CollectionView");
+ }
+ else return false;
+ }
+
render() {
return (
<div className="collectionMenu-cont" >
<div className="collectionMenu">
<div className="collectionViewBaseChrome">
- {this.viewModes}
- {this.templateChrome}
- <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
- <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
- <FontAwesomeIcon icon="filter" size="lg" />
- </button>
- </div>
+ {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes}
+ {!this._buttonizableCommands ? (null) : this.templateChrome}
+ {Doc.UserDoc().noviceMode ? (null) :
+ <Tooltip title={<div className="dash-tooltip">filter documents to show</div>} placement="bottom">
+ <div className="collectionViewBaseChrome-viewSpecs" style={{ display: "grid" }}>
+ <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
+ <FontAwesomeIcon icon="filter" size="lg" />
+ </button>
+ </div>
+ </Tooltip>}
+
+ {this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) :
+ <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom">
+ <button className={"antimodeMenu-button"} key="float"
+ style={{ backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined, borderRight: "1px solid gray" }}
+ onClick={() => DocumentView.FloatDoc(this.props.docView)}>
+ <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} />
+ </button>
+ </Tooltip>}
</div>
{this.subChrome}
</div>
@@ -287,54 +399,78 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionDockingChrome extends React.Component<CollectionMenuProps> {
+ render() {
+ return (null);
+ }
+}
+
+@observer
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean, isDoc?: boolean }> {
public static Instance: CollectionFreeFormViewChrome;
constructor(props: any) {
super(props);
CollectionFreeFormViewChrome.Instance = this;
}
- get Document() { return this.props.CollectionView.props.Document; }
+ get document() { return this.props.docView.props.Document; }
@computed get dataField() {
- return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+ return this.document[Doc.LayoutFieldKey(this.document)];
}
@computed get childDocs() {
return DocListCast(this.dataField);
}
+
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else { return undefined; }
+ }
+ @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get isText() {
+ if (this.selectedDoc) {
+ return this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField;
+ }
+ else return false;
+ }
+
@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
miniMap = (): void => {
- this.Document.hideMinimap = !this.Document.hideMinimap;
+ this.document.hideMinimap = !this.document.hideMinimap;
}
+
private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
private _width = ["1", "5", "10", "100"];
- private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
- private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
- private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
- private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
-
+ private _dotsize = [10, 20, 30, 40];
+ private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"];
+ private _head = ["", "", "", "arrow", "", ""];
+ private _end = ["", "", "arrow", "arrow", "", ""];
+ private _shape = ["", "line", "line", "line", "rectangle", "circle"];
+ private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",];
+ private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
@observable _shapesNum = this._shape.length;
@observable _selected = this._shapesNum;
@@ -397,20 +533,24 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
});
return <div className="btn-draw" key="draw">
{this._draw.map((icon, i) =>
- <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
- style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
- {this._draw[i]}
- </button>)}
+ <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom">
+ <button className="antimodeMenu-button" onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
+ style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
+ <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
+ </button>
+ </Tooltip>)}
</div>;
}
toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => {
- return <button className="antimodeMenu-button" key={key} title={key}
- onPointerDown={action(e => setter())}
- style={{ backgroundColor: value ? "121212" : "" }}>
- <FontAwesomeIcon icon={icon} size="lg" />
- {ele}
- </button>;
+ return <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+ <button className="antimodeMenu-button" key={key}
+ onPointerDown={action(e => setter())}
+ style={{ backgroundColor: value ? "121212" : "" }}>
+ <FontAwesomeIcon icon={icon} size="lg" />
+ {ele}
+ </button>
+ </Tooltip>;
}
@computed get widthPicker() {
@@ -418,12 +558,14 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
return !this._widthBtn ? widthPicker :
<div className="btn2-group" key="width">
{widthPicker}
- {this._width.map(wid =>
- <button className="antimodeMenu-button" key={wid}
- onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
- style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
- {wid}
- </button>)}
+ {this._width.map((wid, i) =>
+ <Tooltip title={<div className="dash-tooltip">change width</div>} placement="bottom">
+ <button className="antimodeMenu-button" key={wid}
+ onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}>
+ •
+ </button>
+ </Tooltip>)}
</div>;
}
@@ -439,6 +581,8 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
{/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
<div className="color-previewII" style={{ backgroundColor: color }} />
+ {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""}
+
</button>)}
</div>;
}
@@ -452,42 +596,65 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
<button className="antimodeMenu-button" key={color}
onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
- <div className="color-previewII" style={{ backgroundColor: color }}></div>
+ <div className="color-previewII" style={{ backgroundColor: color }}>
+ {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""}
+ </div>
</button>)}
</div>;
}
- @computed get formatPane() {
- return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
- onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="chevron-right" size="lg" />
- </button>;
- }
+ @observable viewType = this.selectedDoc?._viewType;
render() {
- return this.Document.isAnnotationOverlay ? (null) :
+ return !this.props.docView.layoutDoc ? (null) :
<div className="collectionFreeFormMenu-cont">
- <div key="map" title="mini map" className="backKeyframe" onClick={this.miniMap}>
- <FontAwesomeIcon icon={"map"} size={"lg"} />
- </div>
- <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
- </div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
- onClick={action(() => this.Document.editing = !this.Document.editing)} >
- {NumCast(this.Document.currentFrame)}
- </div>
- <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
- </div>
-
- {this.widthPicker}
- {this.colorPicker}
- {this.fillPicker}
- {this.drawButtons}
- {this.formatPane}
+ {this.props.docView.props.renderDepth !== 0 || this.isText || this.props.isDoc ? (null) :
+ <Tooltip key="map" title={<div className="dash-tooltip">Toggle Mini Map</div>} placement="bottom">
+ <div className="backKeyframe" onClick={this.miniMap} style={{ marginRight: "5px" }}>
+ <FontAwesomeIcon icon={"map"} size={"lg"} />
+ </div>
+ </Tooltip>
+ }
+ {!this.isText && !this.props.isDoc ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
+ <div className="backKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ </Tooltip> : null}
+ {!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
+ <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
+ onClick={action(() => this.document.editing = !this.document.editing)} >
+ {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">
+ <div className="fwdKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+ </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.isText ?
+ <>
+ {this.drawButtons}
+ {this.widthPicker}
+ {this.colorPicker}
+ {this.fillPicker}
+ </> :
+ (null)
+ }
+ {this.isText ? <RichTextMenu key="rich" /> : null}
</div>;
}
}
@@ -496,12 +663,32 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
- @computed private get descending() { return StrCast(this.props.CollectionView.props.Document._columnsSort) === "descending"; }
- @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
+ get document() { return this.props.docView.props.Document; }
+
+ @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
+ @computed get pivotField() { return StrCast(this.document._pivotField); }
getKeySuggestions = async (value: string): Promise<string[]> => {
value = value.toLowerCase();
- const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+ const docs = DocListCast(this.document[this.props.fieldKey]);
+
+ if (Doc.UserDoc().noviceMode) {
+ 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] !== "_"));
+ return keys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -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] !== "_"));
+ return noviceKeys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1);
+ }
+ }
+
if (docs instanceof Doc) {
return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
} else {
@@ -536,14 +723,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@action
setValue = (value: string) => {
- this.props.CollectionView.props.Document._pivotField = value;
+ this.document._pivotField = value;
return true;
}
@action toggleSort = () => {
- this.props.CollectionView.props.Document._columnsSort =
- this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
- this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+ this.document._columnsSort =
+ this.document._columnsSort === "descending" ? "ascending" :
+ this.document._columnsSort === "ascending" ? undefined : "descending";
}
@action resetValue = () => { this._currentKey = this.pivotField; };
@@ -593,35 +780,36 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@observer
export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
- // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ get document() { return this.props.docView.props.Document; }
@undoBatch
togglePreview = () => {
const dividerWidth = 4;
const borderWidth = Number(COLLECTION_BORDER_WIDTH);
- const panelWidth = this.props.CollectionView.props.PanelWidth();
- const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+ const panelWidth = this.props.docView.props.PanelWidth();
+ const previewWidth = NumCast(this.document.schemaPreviewWidth);
const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
- this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+ this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
}
@undoBatch
@action
toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []);
if (textwrappedRows.length) {
- this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+ this.document.textwrappedSchemaRows = new List<string>([]);
} else {
- const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+ const docs = DocListCast(this.document[this.props.fieldKey]);
const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ this.document.textwrappedSchemaRows = new List<string>(allRows);
}
}
render() {
- const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
- const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ const previewWidth = NumCast(this.document.schemaPreviewWidth);
+ const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
return (
<div className="collectionSchemaViewChrome-cont">
@@ -641,11 +829,12 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionMenuPr
@observer
export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
+ get document() { return this.props.docView.props.Document; }
get sortAscending() {
- return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
+ return this.document[this.props.fieldKey + "-sortAscending"];
}
set sortAscending(value) {
- this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+ this.document[this.props.fieldKey + "-sortAscending"] = value;
}
@computed private get ascending() {
return Cast(this.sortAscending, "boolean", null);
@@ -676,15 +865,16 @@ export class CollectionTreeViewChrome extends React.Component<CollectionMenuProp
// Enter scroll speed for 3D Carousel
@observer
export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
+ get document() { return this.props.docView.props.Document; }
@computed get scrollSpeed() {
- return this.props.CollectionView.props.Document._autoScrollSpeed;
+ return this.document._autoScrollSpeed;
}
@action
setValue = (value: string) => {
const numValue = Number(StrCast(value));
if (numValue > 0) {
- this.props.CollectionView.props.Document._autoScrollSpeed = numValue;
+ this.document._autoScrollSpeed = numValue;
return true;
}
return false;
@@ -721,13 +911,14 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
private decrementLimitReached: boolean = false;
@observable private resize = false;
private resizeListenerDisposer: Opt<Lambda>;
+ get document() { return this.props.docView.props.Document; }
componentDidMount() {
- runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+ runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700);
// listener to reduce text on chrome resize (panel resize)
- this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+ this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
runInAction(() => this.resize = newValue < 700);
});
}
@@ -736,7 +927,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
this.resizeListenerDisposer?.();
}
- get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+ get numCols() { return NumCast(this.document.gridNumCols, 10); }
/**
* Sets the value of `numCols` on the grid's Document to the value entered.
@@ -745,7 +936,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === "Tab") {
if (e.currentTarget.valueAsNumber > 0) {
- this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+ this.document.gridNumCols = e.currentTarget.valueAsNumber;
}
}
@@ -757,8 +948,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
// @undoBatch
// onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
// if (e.key === "Enter" || e.key === "Tab") {
- // if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
- // this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+ // if (e.currentTarget.valueAsNumber > 0 && this.document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+ // this.document.rowHeight = e.currentTarget.valueAsNumber;
// }
// }
// }
@@ -768,7 +959,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
@undoBatch
toggleFlex = () => {
- this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+ this.document.gridFlex = !BoolCast(this.document.gridFlex, true);
}
/**
@@ -776,8 +967,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
onIncrementButtonClick = () => {
this.clicked = true;
- this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
- undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+ this.entered && (this.document.gridNumCols as number)--;
+ undoBatch(() => this.document.gridNumCols = this.numCols + 1)();
this.entered = false;
}
@@ -787,8 +978,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
onDecrementButtonClick = () => {
this.clicked = true;
if (!this.decrementLimitReached) {
- this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
- undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+ this.entered && (this.document.gridNumCols as number)++;
+ undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
}
this.entered = false;
}
@@ -799,7 +990,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
incrementValue = () => {
this.entered = true;
if (!this.clicked && !this.decrementLimitReached) {
- this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+ this.document.gridNumCols = this.numCols + 1;
}
this.decrementLimitReached = false;
this.clicked = false;
@@ -812,7 +1003,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
this.entered = true;
if (!this.clicked) {
if (this.numCols !== 1) {
- this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+ this.document.gridNumCols = this.numCols - 1;
}
else {
this.decrementLimitReached = true;
@@ -826,7 +1017,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
* Toggles the value of preventCollision
*/
toggleCollisions = () => {
- this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+ this.document.gridPreventCollision = !this.document.gridPreventCollision;
}
/**
@@ -834,7 +1025,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
// need to change startCompaction so that this operation will be undoable.
- this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+ this.document.gridStartCompaction = e.target.selectedOptions[0].value;
}
render() {
@@ -852,10 +1043,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
<span className="grid-icon">
<FontAwesomeIcon icon="text-height" size="1x" />
</span>
- <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
</span> */}
<span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
- <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+ <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
<label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
</span>
@@ -863,7 +1054,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
style={{ marginRight: 5 }}
onPointerDown={stopPropagation}
onChange={this.changeCompactType}
- value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+ value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}>
{["vertical", "horizontal", "none"].map(type =>
<option className="collectionGridViewChrome-viewOption"
onPointerDown={stopPropagation}
@@ -875,11 +1066,11 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
<span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
<input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
- checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+ checked={BoolCast(this.document.gridFlex, true)} />
<label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
</span>
- <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+ <button onClick={() => this.document.gridResetLayout = true}>
{!this.resize ? "Reset" :
<FontAwesomeIcon icon="redo-alt" size="1x" />}
</button>
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 22a3877ab..2e4055256 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -39,7 +39,15 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
@computed get contents() {
return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
- <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
+ <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine}
+ addDocument={(doc: Doc | Doc[]) => {
+ (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
+ return this.props.addDocument(doc);
+ }}
+ moveDocument={(doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
+ (doc instanceof Doc ? [doc] : doc).map((d) => Doc.deiconifyView(d));
+ return this.props.moveDocument(doc, targetCollection, addDoc);
+ }} />
</div>;
}
toggleStarburst = action(() => {
@@ -72,24 +80,13 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}
});
- @undoBatch
- @action
- onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (super.onInternalDrop(e, de)) {
- if (de.complete.docDragData) {
- DocUtils.pileup(this.childDocs);
- }
- }
- return true;
- }
-
_undoBatch: UndoManager.Batch | undefined;
pointerDown = (e: React.PointerEvent) => {
let dist = 0;
SnappingManager.SetIsDragging(true);
// this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
- if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
+ if (this.layoutEngine() === "pass" && this.childDocs.length && e.shiftKey) {
dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
if (dist > 100) {
if (!this._undoBatch) {
@@ -110,11 +107,11 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
if (!this.childDocs.length) {
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
}
- }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap
+ }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap
}
onClick = (e: React.MouseEvent) => {
- if (e.button === 0 && this._doubleTap) {
+ if (e.button === 0) {//} && this._doubleTap) {
SelectionManager.DeselectAll();
this.toggleStarburst();
e.stopPropagation();
@@ -124,7 +121,6 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
render() {
return <div className={"collectionPileView"} onClick={this.onClick} onPointerDown={this.pointerDown}
- ref={this.createDashEventsTarget}
style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
{this.contents}
</div>;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index eecaf7672..d11d6a5ba 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -32,6 +32,7 @@ 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";
const path = require('path');
library.add(faExpand);
@@ -193,7 +194,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
const onItemDown = (e: React.PointerEvent) => {
- fieldIsDoc && SetupDrag(this._focusRef,
+ //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);
@@ -240,25 +242,72 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// <FontAwesomeIcon icon="expand" size="sm" />
// </div>
// );
- trace();
-
-
-
+ const positions = [];
+ if (StrCast(this.props.Document._searchString).toLowerCase() !== "") {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
+ let term = "";
+ if (cfield !== undefined) {
+ if (cfield.Text !== undefined) {
+ term = cfield.Text;
+ }
+ else if (StrCast(cfield)) {
+ term = StrCast(cfield);
+ }
+ else {
+ term = String(NumCast(cfield));
+ }
+ }
+ term = term.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) {
+ term = term.slice(start + search.length + 1);
+ tally += start + search.length + 1;
+ start = term.indexOf(search);
+ positions.push(tally + start);
+ }
+ if (positions.length > 1) {
+ positions.pop();
+ }
+ }
return (
- <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
+ <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "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]}>
-
-
<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)}
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
placeholder={"enter value"}
+ bing={() => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
+ if (cfield !== undefined) {
+ console.log(typeof (cfield));
+ // if (typeof(cfield)===RichTextField)
+ const a = cfield as RichTextField;
+ if (a.Text !== undefined) {
+ return (a.Text);
+ }
+ else if (StrCast(cfield)) {
+ return StrCast(cfield);
+ }
+ else {
+ return String(NumCast(cfield));
+ }
+ }
+ }}
GetValue={() => {
if (type === "number" && (contents === 0 || contents === "0")) {
return "0";
@@ -272,6 +321,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) :
Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
return val;
+
}
}}
@@ -827,3 +877,69 @@ 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);
+ doc.searchIndex = NumCast(doc.searchIndex);
+ }} style={{ padding: 2, left: 77 }}>
+ <FontAwesomeIcon icon="arrow-up" size="sm" />
+ </button>
+ <button onClick={() => {
+ {
+ doc.searchMatchAlt = false;
+ setTimeout(() => doc.searchMatchAlt = true, 0);
+ doc.searchIndex = NumCast(doc.searchIndex);
+ }
+ }} 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);
+ doc.searchIndex = NumCast(doc.searchIndex);
+ }}>
+ <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>
+ );
+ }
+}
+
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index efff4db98..e65adcf76 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -9,6 +9,8 @@ import { ColumnType } from "./CollectionSchemaView";
import { faFile } from "@fortawesome/free-regular-svg-icons";
import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
+import { Doc } from "../../../fields/Doc";
+import { StrCast } from "../../../fields/Types";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -76,14 +78,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe
-
-
-
-
-
-
-
-
export interface ColumnMenuProps {
columnField: SchemaHeaderField;
// keyValue: string;
@@ -288,9 +282,10 @@ export interface KeysDropdownProps {
existingKeys: string[];
canAddNew: boolean;
addNew: boolean;
- onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
+ onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void;
setIsEditing: (isEditing: boolean) => void;
width?: string;
+ docs?: Doc[];
}
@observer
export class KeysDropdown extends React.Component<KeysDropdownProps> {
@@ -306,10 +301,23 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
@action
onSelect = (key: string): void => {
- this.props.onSelect(this._key, key, this.props.addNew);
- this.setKey(key);
+ if (key.slice(0, this._key.length) === this._key && this._key !== key) {
+ const filter = key.slice(this._key.length - key.length);
+ this.props.onSelect(this._key, this._key, this.props.addNew, filter);
+ }
+ else {
+ this.props.onSelect(this._key, key, this.props.addNew);
+ this.setKey(key);
+ this._isOpen = false;
+ this.props.setIsEditing(false);
+ }
+ }
+
+ @action
+ onSelect2 = (key: string): void => {
+ this._searchTerm = this._searchTerm.slice(0, this._key.length) + key;
this._isOpen = false;
- this.props.setIsEditing(false);
+
}
@undoBatch
@@ -371,22 +379,53 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
});
// if search term does not already exist as a group type, give option to create new group type
- if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
- options.push(<div key={""} className="key-option" style={{
+ if (this._key !== this._searchTerm.slice(0, this._key.length)) {
+ 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"
+ }}
+ onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
+ Create "{this._searchTerm}" key</div>);
+ }
+ }
+
+ return options;
+ }
+
+ renderFilterOptions = (): JSX.Element[] | JSX.Element => {
+ if (!this._isOpen) return <></>;
+ const keyOptions: string[] = [];
+ const temp = this._searchTerm.slice(this._key.length);
+ this.props.docs?.forEach((doc) => {
+ const key = StrCast(doc[this._key]);
+ if (keyOptions.includes(key) === false && key.includes(temp)) {
+ keyOptions.push(key);
+ }
+ });
+
+
+ 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"
}}
- onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
- Create "{this._searchTerm}" key</div>);
- }
+ onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect2(key); }}>{key}</div>;
+ });
return options;
}
+
render() {
return (
- <div className="keys-dropdown" style={{ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" }}>
- <input className="keys-search" //style={{ width: this.props.width, maxWidth: "1000" }}
+ <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}>
+ {this._key === this._searchTerm.slice(0, this._key.length) ?
+ <div style={{ position: "absolute", marginLeft: "4px", marginTop: "3", color: "grey", pointerEvents: "none", lineHeight: 1.15 }}>
+ {this._key}
+ </div>
+ : undefined}
+ <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) => {
@@ -395,10 +434,11 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
}} onFocus={this.onFocus} onBlur={this.onBlur}></input>
<div className="keys-options-wrapper" style={{
backgroundColor: "white",
- width: this.props.width, maxWidth: this.props.width, overflowX: "hidden"
+ width: this.props.width, maxWidth: this.props.width,
}}
onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}>
- {this.renderOptions()}
+ {this._key === this._searchTerm.slice(0, this._key.length) ?
+ this.renderFilterOptions() : this.renderOptions()}
</div>
</div >
);
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index b77173b25..dade4f2f2 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -209,6 +209,14 @@ export class MovableRow extends React.Component<MovableRowProps> {
return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
}
+ @action
+ onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ console.log("yes");
+ if (e.key === "Backspace" || e.key === "Delete") {
+ undoBatch(() => this.props.removeDoc(this.props.rowInfo.original));
+ }
+ }
+
render() {
const { children = null, rowInfo } = this.props;
if (!rowInfo) {
@@ -227,14 +235,14 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (this.props.rowWrapped) className += " row-wrapped";
return (
- <div className={className} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
- <div className="collectionSchema-row-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <ReactTableDefaults.TrComponent>
- <div className="row-dragger">
+ <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> */}
{children}
</ReactTableDefaults.TrComponent>
</div>
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 5226a60f1..ba0a259c5 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -6,7 +6,7 @@
border-style: solid;
border-radius: $border-radius;
box-sizing: border-box;
- position: absolute;
+ position: relative;
top: 0;
width: 100%;
height: 100%;
@@ -25,7 +25,6 @@
.collectionSchemaView-tableContainer {
width: 100%;
height: 100%;
- overflow: scroll;
}
.collectionSchemaView-dividerDragger {
@@ -59,9 +58,7 @@
}
.rt-thead {
- width: calc(100% - 52px);
- margin-left: 50px;
-
+ width: 100%;
z-index: 100;
overflow-y: visible;
@@ -96,7 +93,7 @@
}
.rt-tbody {
- width: calc(100% - 2px);
+ width: 100%;
direction: rtl;
overflow: visible;
}
@@ -164,16 +161,6 @@
.collectionSchema-col {
height: 100%;
-
- .collectionSchema-col-wrapper {
- &.col-before {
- border-left: 2px solid red;
- }
-
- &.col-after {
- border-right: 2px solid red;
- }
- }
}
@@ -297,7 +284,6 @@ button.add-column {
background-color: white;
border: 1px solid lightgray;
padding: 2px 3px;
- overflow-x: hidden;
&:not(:first-child) {
border-top: 0;
@@ -525,14 +511,21 @@ button.add-column {
.collectionSchemaView-table {
width: 100%;
height: 100%;
- overflow: visible;
}
.reactTable-sub {
padding: 10px 30px;
background-color: rgb(252, 252, 252);
- width: calc(100% - 50px);
- margin-left: 50px;
+ width: 100%;
+
+ .rt-thead {
+ display:none;
+ }
+ .collectionSchemaView-table{
+ border: solid 1px;
+ overflow: hidden;
+ }
+
.row-dragger {
background-color: rgb(252, 252, 252);
@@ -567,7 +560,6 @@ button.add-column {
text-transform: uppercase;
cursor: pointer;
font-size: 10.5px;
- padding: 10px;
margin-left: 50px;
margin-top: 10px;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 5553bbbb7..a003de0d3 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -4,27 +4,26 @@ import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free-
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 { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
-import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { Cast, NumCast } from "../../../fields/Types";
+import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils";
+import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
import '../DocumentDecorations.scss';
+import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
import { KeysDropdown } from "./CollectionSchemaHeaders";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
-import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
-import { SnappingManager } from "../../util/SnappingManager";
-import Measure from "react-measure";
import { SchemaTable } from "./SchemaTable";
-import { TraceMobx } from "../../../fields/util";
library.add(faCog, faPlus, faSortUp, faSortDown);
library.add(faTable);
@@ -170,6 +169,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
const columns = this.columns;
+ columns.forEach(col => col.setDesc(undefined));
+
const index = columns.findIndex(c => c.heading === columnField.heading);
const column = columns[index];
column.setDesc(descending);
@@ -253,7 +254,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div className="collectionSchema-headerMenu-group">
<div onClick={() => this.typesDropdownChange(!this._openTypes)}>
<label>Column type:</label>
- <FontAwesomeIcon icon={"caret-down"} size="sm" style={{ float: "right" }} />
+ <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right" }} />
</div>
{this._openTypes ? allColumnTypes : justColType}
</div >
@@ -310,7 +311,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@undoBatch
@action
- changeColumns = (oldKey: string, newKey: string, addNew: boolean) => {
+ changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
const columns = this.columns;
if (columns === undefined) {
this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
@@ -325,6 +326,20 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
column.setHeading(newKey);
columns[index] = column;
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;
+ }
+ }
}
}
}
@@ -591,6 +606,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
}
+
+
+ onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ }
render() {
TraceMobx();
const menuContent = this.renderMenuContent;
@@ -608,14 +627,15 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
{({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
</Measure>
</div>;
-
return <div className="collectionSchemaView-container"
style={{
+ overflow: this.props.overflow === true ? "auto" : undefined,
pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
- width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%"
+ width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative",
}} >
<div className="collectionSchemaView-tableContainer"
- style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
+ style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }}
+ onKeyPress={this.onKeyPress}
onPointerDown={this.onPointerDown}
onWheel={e => this.props.active(true) && e.stopPropagation()}
onDrop={e => this.onExternalDrop(e, {})}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index dd4c34885..fe3d57bdb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -45,7 +45,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
@observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField)); }
@computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
- @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
+ @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 gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
@@ -184,7 +184,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
if (found) {
const top = found.getBoundingClientRect().top;
const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
- smoothScroll(500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+ smoothScroll(doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
}
afterFocus && setTimeout(() => {
if (afterFocus?.()) { }
@@ -208,7 +208,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
NativeHeight={returnZero}
NativeWidth={returnZero}
fitToBox={false}
- dontRegisterView={this.props.dontRegisterView}
+ dontRegisterView={BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
rootSelected={this.rootSelected}
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
@@ -227,6 +227,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
addDocTab={this.addDocTab}
bringToFront={returnFalse}
ContentScaling={returnOne}
+ scriptContext={this.props.scriptContext}
pinToPres={this.props.pinToPres}
/>;
}
@@ -286,19 +287,31 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
}
});
if (super.onInternalDrop(e, de)) {
- const newDoc = de.complete.docDragData.droppedDocuments[0];
+ const newDocs = de.complete.docDragData.droppedDocuments;
const docs = this.childDocList;
if (docs) {
- if (targInd === -1) targInd = docs.length;
- else targInd = docs.indexOf(this.filteredChildren[targInd]);
- const srcInd = docs.indexOf(newDoc);
- docs.splice(srcInd, 1);
- docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, newDoc);
+ newDocs.map((doc, i) => {
+ console.log(doc.title);
+ if (i === 0) {
+ if (targInd === -1) targInd = docs.length;
+ else targInd = docs.indexOf(this.filteredChildren[targInd]);
+ const srcInd = docs.indexOf(doc);
+ docs.splice(srcInd, 1);
+ docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc);
+ } else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated
+ if (targInd === -1) targInd = docs.length;
+ else targInd = docs.indexOf(newDocs[0]) + 1;
+ const srcInd = docs.indexOf(doc);
+ docs.splice(srcInd, 1);
+ docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc);
+ }
+ });
}
}
}
return false;
}
+
@undoBatch
@action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
@@ -481,7 +494,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
})}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={e => this.props.active() && e.stopPropagation()} >
+ onWheel={e => this.props.active(true) && e.stopPropagation()} >
{this.renderedSections}
{!this.showAddAGroup ? (null) :
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 76af70cd1..f193a9787 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -12,6 +12,7 @@ import { NumCast, StrCast, Cast } from "../../../fields/Types";
import { ImageField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -298,7 +299,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
oneLine: true,
HeadingObject: this.props.headingObject,
toggle: this.toggleVisibility,
- color: this._color
};
const newEditableViewProps = {
GetValue: () => "",
@@ -306,7 +306,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
contents: "+ NEW",
HeadingObject: this.props.headingObject,
toggle: this.toggleVisibility,
- color: this._color
};
const headingView = this.props.headingObject ?
<div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
@@ -332,11 +331,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
{this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
- {evContents === `NO ${key.toUpperCase()} VALUE` ?
- (null) :
- <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
- <FontAwesomeIcon icon="trash" size="lg" />
- </button>}
+ {<button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+ <FontAwesomeIcon icon="trash" size="lg" />
+ </button>}
{evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionOptions">
<Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
@@ -350,12 +347,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div> : (null);
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
const chromeStatus = this.props.parent.props.Document._chromeStatus;
-
+ const type = this.props.parent.props.Document.type;
return <>
{this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
{
this.collapsed ? (null) :
- <div style={{ marginTop: 5 }}>
+ <div>
<div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
style={{
padding: singleColumn ? `${columnYMargin}px ${0}px ${style.yMargin}px ${0}px` : `${columnYMargin}px ${0}px`,
@@ -370,9 +367,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
{this.props.parent.children(this.props.docList, uniqueHeadings.length)}
{singleColumn ? (null) : this.props.parent.columnDragger}
</div>
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
+ {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ?
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
- style={{ width: style.columnWidth / style.numGroupColumns }}>
+ style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 70 }}>
<EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
</div> : null}
</div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8480a56cc..0e40cd21c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
import { basename } from 'path';
import CursorField from "../../../fields/CursorField";
-import { Doc, Opt, Field } from "../../../fields/Doc";
+import { Doc, Opt, Field, DocListCast } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
@@ -55,17 +55,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _mainCont?: HTMLDivElement;
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer?.();
this.gestureDisposer?.();
- this.multiTouchDisposer?.();
+ this._multiTouchDisposer?.();
if (ele) {
this._mainCont = ele;
this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
- this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
+ this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -74,7 +74,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
componentWillUnmount() {
this.gestureDisposer?.();
- this.multiTouchDisposer?.();
+ this._multiTouchDisposer?.();
}
@computed get dataDoc() {
@@ -112,9 +112,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
[...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
}
@computed get childDocs() {
+ let rawdocs: (Doc | Promise<Doc>)[] = DocListCast(this.props.Document._searchDocs);
- let rawdocs: (Doc | Promise<Doc>)[] = [];
- if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
+ if (rawdocs.length !== 0) {
+ } else if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
rawdocs = [this.dataField];
} else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it.
rawdocs = Cast(this.dataField, listSpec(Doc), null);
@@ -126,11 +127,66 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
- const docFilters = this.docFilters();
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+ let childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+
+ const searchDocs = DocListCast(this.props.Document._searchDocs);
+ // if (searchDocs !== undefined && searchDocs.length > 0) {
+ // let newdocs: Doc[] = [];
+ // childDocs.forEach((el) => {
+ // searchDocs.includes(el) ? newdocs.push(el) : undefined;
+ // });
+ // childDocs = newdocs;
+ // }
+
+ let docsforFilter: Doc[] = childDocs;
+ if (searchDocs !== undefined && searchDocs.length > 0) {
+ docsforFilter = [];
+ // let newdocs: Doc[] = [];
+ // let newarray: Doc[] = [];
+ //while (childDocs.length > 0) {
+ //newarray = [];
+ childDocs.forEach((d) => {
+ if (d.data !== undefined) {
+ console.log(d);
+ let newdocs = DocListCast(d.data);
+ if (newdocs.length > 0) {
+ let vibecheck: 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)) {
+ vibecheck = true;
+ }
+ });
+ newdocs = newarray;
+ }
+ if (vibecheck === true) {
+ docsforFilter.push(d);
+ }
+ }
+ }
+ if (searchDocs.includes(d)) {
+ docsforFilter.push(d);
+ }
+ });
+ //childDocs = newarray;
+ //}
+ }
+ childDocs = docsforFilter;
+
+
+ const docFilters = this.docFilters();
const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript);
+ return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, this.docFilters(), docRangeFilters, viewSpecScript);
}
@action
@@ -185,18 +241,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
@action
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
- ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
if (docDragData) {
let added = false;
- const dropaction = docDragData.dropAction || docDragData.userDropAction;
- if (dropaction && dropaction !== "move") {
- added = this.addDocument(docDragData.droppedDocuments);
- } else if (docDragData.moveDocument) {
+ const dropAction = docDragData.dropAction || docDragData.userDropAction;
+ if ((!dropAction || dropAction === "move") && docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
const res = addedDocs.length ? this.addDocument(addedDocs) : true;
- added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document) || de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res;
+ if (movedDocs.length) {
+ const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || !this.props.isAnnotationOverlay ||
+ 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;
} else {
+ ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
added = this.addDocument(docDragData.droppedDocuments);
}
added && e.stopPropagation();
@@ -292,7 +350,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
- Doc.GetProto(htmlDoc)["data-text"] = text;
+ Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text;
this.props.addDocument(htmlDoc);
if (srcWeb) {
const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
@@ -300,7 +358,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
const x = (rect?.x || 0);
const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
- const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+ const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 75, _height: 40, x, y, annotationOn: srcWeb });
anchor.context = srcWeb;
const key = Doc.LayoutFieldKey(srcWeb);
Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
@@ -349,8 +407,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
title: uriList,
_width: 400,
_height: 315,
- _nativeWidth: 600,
- _nativeHeight: 472.5
+ _nativeWidth: 850,
+ _nativeHeight: 962
}));
return;
}
@@ -430,8 +488,7 @@ import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { DocumentType } from "../../documents/DocumentTypes";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
-import { CollectionView } from "./CollectionView";
+import { CollectionView, CollectionViewType } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
import { setTimeout } from "timers";
-
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 50f0534bd..c9bf82406 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -22,7 +22,7 @@
ul {
list-style: none;
padding-left: 20px;
- margin-bottom: 1px;// otherwise vertical scrollbars may pop up for no apparent reason....
+ margin-bottom: 1px; // otherwise vertical scrollbars may pop up for no apparent reason....
}
@@ -35,7 +35,7 @@
width: 15px;
color: $intermediate-color;
margin-top: 3px;
- transform: scale(1.3, 1.3);
+ transform: scale(1.3, 1.3);
border: #80808030 1px solid;
border-radius: 4px;
}
@@ -67,8 +67,10 @@
margin-left: 3px;
display: none;
}
+
.collectionTreeView-keyHeader:hover {
background: #797777;
+ cursor: pointer;
}
.collectionTreeView-subtitle {
@@ -89,8 +91,10 @@
height: 17px;
width: 15px;
}
+
.treeViewItem-openRight:hover {
background: #797777;
+ cursor: pointer;
}
.treeViewItem-border {
@@ -106,10 +110,12 @@
.editableView-container-editing-oneLine {
min-width: 15px;
}
+
.documentView-node-topmost {
width: unset;
}
- > svg {
+
+ >svg {
display: none;
}
@@ -119,7 +125,8 @@
.collectionTreeView-keyHeader {
display: inherit;
}
- > svg {
+
+ >svg {
display: inherit;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 651357e5d..3c7471d7c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -90,7 +90,10 @@ class TreeView extends React.Component<TreeViewProps> {
get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive
get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); }
@observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
- set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; }
+ 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 MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
@@ -100,8 +103,8 @@ class TreeView extends React.Component<TreeViewProps> {
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 ? Cast(layout[field], listSpec(Doc)) : undefined) || // else if there's a layout doc, display it's fields
- Cast(this.doc[field], listSpec(Doc))) as Doc[]; // otherwise use the document'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
}
@computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
@@ -328,7 +331,7 @@ class TreeView extends React.Component<TreeViewProps> {
[...this.props.renderedIds, this.doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
- return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.doc[Id] + this.doc.title}>
+ return <ul key={this.doc[Id] + this.doc.title}><div ref={this._dref} style={{ display: "inline-block" }} >
{this.expandedField}
</div></ul>;
} else {
@@ -782,7 +785,7 @@ 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"
});
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index b630f9cf8..a5aef86de 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -24,6 +24,7 @@
border-right: unset;
z-index: 2;
}
+
.collectionTimeView-treeView {
display: flex;
flex-direction: column;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a82c91383..4d1cb670c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, SharingPermissions, distributeAcls } from '../../../fields/util';
import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -27,12 +27,10 @@ import { InteractionUtils } from '../../util/InteractionUtils';
import { UndoManager } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import { ScriptBox } from '../ScriptBox';
import { Touchable } from '../Touchable';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionDockingView } from "./CollectionDockingView";
-import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
import { CollectionLinearView } from './CollectionLinearView';
@@ -47,7 +45,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import CollectionMenu from './CollectionMenu';
import { ContextMenuProps } from '../ContextMenuItem';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -77,10 +74,11 @@ export enum CollectionViewType {
Pile = "pileup"
}
export interface CollectionViewCustomProps {
- filterAddDocument: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+ filterAddDocument?: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
childLayoutTemplate?: () => Opt<Doc>; // specify a layout Doc template to use for children of the collection
childLayoutString?: string; // specify a layout string to use for children of the collection
childOpacity?: () => number;
+ hideFilter?: true;
}
export interface CollectionRenderProps {
@@ -90,6 +88,7 @@ export interface CollectionRenderProps {
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
PanelWidth: () => number;
+ PanelHeight: () => number;
ChildLayoutTemplate?: () => Doc;
ChildLayoutString?: string;
}
@@ -105,7 +104,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@observable private static _safeMode = false;
public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private AclMap = new Map<symbol, string>([
[AclPrivate, SharingPermissions.None],
@@ -145,18 +144,16 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const effectiveAcl = GetEffectiveAcl(this.props.Document);
if (added.length) {
- if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
+ if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
return false;
}
else {
if (this.props.Document[AclSym]) {
- // change so it only adds if more restrictive
added.forEach(d => {
- // const dataDoc = d[DataSym];
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
+ if (d.author === Doc.CurrentUserEmail && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
+ else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
}
- // dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
});
}
@@ -182,7 +179,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
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] = 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()));
}
}
@@ -192,14 +190,16 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
- const effectiveAcl = GetEffectiveAcl(this.props.Document);
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || getPlaygroundMode()) {
+ const collectionEffectiveAcl = GetEffectiveAcl(this.props.Document);
+ const docEffectiveAcl = GetEffectiveAcl(doc);
+ // you can remove the document if you either have Admin/Edit access to the collection or to the specific document
+ if (collectionEffectiveAcl === AclEdit || collectionEffectiveAcl === AclAdmin || docEffectiveAcl === AclAdmin || docEffectiveAcl === AclEdit) {
const docs = doc instanceof Doc ? [doc] : doc as Doc[];
const targetDataDoc = this.props.Document[DataSym];
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
- const result = value.filter(v => !docs.includes(v));
- if (result.length !== value.length) {
- targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+ const toRemove = value.filter(v => docs.includes(v));
+ if (toRemove.length !== 0) {
+ toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc));
return true;
}
}
@@ -289,9 +289,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
- if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
- subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
- }
addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
const existingVm = ContextMenu.Instance.findByDescription(category);
@@ -312,7 +309,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const options = cm.findByDescription("Options...");
const optionItems = options && "subitems" in options ? options.subitems : [];
- 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" });
+ !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" });
}
@@ -341,7 +338,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
icon: "edit",
event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
}));
- !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
if (!Doc.UserDoc().noviceMode) {
const more = cm.findByDescription("More...");
@@ -371,7 +368,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
get _facetWidth() { return NumCast(this.props.Document._facetWidth); }
set _facetWidth(value) { this.props.Document._facetWidth = value; }
- bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
+ bodyPanelWidth = () => this.props.PanelWidth();
facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth));
@computed get dataDoc() {
@@ -493,6 +490,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return false;
}), 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() {
@@ -524,6 +522,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
</div>
<div className="collectionTimeView-tree" key="tree">
<CollectionTreeView
+ PanelPosition={""}
Document={facetCollection}
DataDoc={facetCollection}
fieldKey={`${this.props.fieldKey}-filter`}
@@ -562,6 +561,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
</div>
</div>;
}
+
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString);
@@ -574,16 +574,16 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
active: this.active,
whenActiveChanged: this.whenActiveChanged,
PanelWidth: this.bodyPanelWidth,
+ PanelHeight: this.props.PanelHeight,
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
- setTimeout(action(() => this.props.isSelected(true) && (CollectionMenu.Instance.SelectedCollection = this)), 0);
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")}`;
return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}>
{this.showIsTagged()}
- <div className="collectionView-facetCont" style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
+ <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)}
</div>
{this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
@@ -592,11 +592,11 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
:
""))}
- {(!this.props.isSelected() || this.props.Document.hideFilterView) && !this.props.Document.forceActive ? (null) :
+ {(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%" : "55%" }} />
}
- {this.facetWidth() < 10 ? (null) : this.filterView}
+ {Doc.UserDoc()?.noviceMode || this.facetWidth() < 10 ? (null) : this.filterView}
</div>);
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index 4e704b58f..bc9cf4848 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -2,11 +2,13 @@
div {
overflow: visible !important;
}
+
.metadataEntry-outerDiv {
overflow: hidden !important;
pointer-events: all;
}
}
+
.parentDocumentSelector-flyout {
position: relative;
z-index: 9999;
@@ -31,26 +33,31 @@
border-left: 0px;
}
}
+
.parentDocumentSelector-button {
- pointer-events: all;
+ pointer-events: all;
position: relative;
display: inline-block;
+
svg {
- width:20px !important;
- height:20px;
+ // 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;
+
+ display: inline-block;
width:100%;
height:100%;
} \ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 8c0b8de9d..4c8cac3ed 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -42,14 +42,14 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
async fetchDocuments() {
const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document));
const containerProtoSets = await Promise.all(aliases.map(async alias =>
- await Promise.all((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
+ ((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 (await SearchUtil.GetAliasesOfDocument(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 (await SearchUtil.GetAliasesOfDocument(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(() => {
@@ -129,7 +129,7 @@ export class DockingViewButtonSelector extends React.Component<{ views: () => Do
this.props.views()[0]?.select(false);
}} className="buttonSelector">
<Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
- <FontAwesomeIcon icon={"cog"} size={"sm"} />
+ <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
</Flyout>
</span>;
}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index cde795098..a974c5496 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -11,22 +11,22 @@ import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } 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";
import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
import '../DocumentDecorations.scss';
-import { CellProps, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaDateCell } from "./CollectionSchemaCells";
+import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
+import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
import "./CollectionSchemaView.scss";
import { CollectionView } from "./CollectionView";
-import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { emptyFunction, returnZero, returnOne, returnFalse, returnEmptyFilter, emptyPath } from "../../../Utils";
-import { TouchScrollableMenuItem } from "../TouchScrollableMenu";
enum ColumnType {
@@ -128,7 +128,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@computed get sorted(): SortingRule[] {
return this.props.columns.reduce((sorted, shf) => {
- shf.desc && sorted.push({ id: shf.heading, desc: shf.desc });
+ shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc });
return sorted;
}, [] as SortingRule[]);
}
@@ -148,7 +148,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@action
- changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown;
+ changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@computed get tableColumns(): Column<Doc>[] {
@@ -160,7 +160,7 @@ 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 === "collection", false)) {
+ if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) {
columns.push(
{
expander: true,
@@ -188,7 +188,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
addNew={false}
onSelect={this.props.changeColumns}
setIsEditing={this.props.setHeaderIsEditing}
-
+ docs={this.props.childDocs}
// try commenting this out
width={"100%"}
/>;
@@ -208,7 +208,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}}>
{col.heading}</div>;
- const sortIcon = col.desc === undefined ? "circle" : col.desc === true ? "caret-down" : "caret-up";
+ const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
const header =
<div //className="collectionSchemaView-header"
@@ -216,21 +216,21 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
className="collectionSchemaView-menuOptions-wrapper"
style={{
background: col.color, padding: "2px",
- display: "flex"
+ display: "flex", cursor: "default", height: "100%",
}}>
- <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingLeft: "7px" }} />
- <div className="keys-dropdown"
- style={{ display: "inline", zIndex: 1000 }}>
- {keysDropdown}
- </div>
+ <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px" }} />
+ {/* <div className="keys-dropdown"
+ style={{ display: "inline", zIndex: 1000 }}> */}
+ {keysDropdown}
+ {/* </div> */}
<div onClick={e => this.changeSorting(col)}
- style={{ paddingRight: "6px", display: "inline" }}>
- <FontAwesomeIcon icon={sortIcon} size="sm" />
+ style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit" }}>
+ <FontAwesomeIcon icon={sortIcon} size="lg" />
</div>
- <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)}
- style={{ float: "right", paddingRight: "6px" }}>
+ {/* <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)}
+ style={{ float: "right", paddingRight: "6px", zIndex: 1, background: "inherit" }}>
<FontAwesomeIcon icon={"compass"} size="sm" />
- </div>
+ </div> */}
</div>;
return {
@@ -283,13 +283,62 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
accessor: (doc: Doc) => 0,
id: "add",
- Cell: (rowProps: CellInfo) => <></>,
+ Cell: (rowProps: CellInfo) => {
+ 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,
+ col: columnIndex,
+ rowProps: rowProps,
+ isFocused: isFocused,
+ changeFocusedCellByIndex: this.changeFocusedCellByIndex,
+ CollectionView: this.props.CollectionView,
+ ContainingCollection: this.props.ContainingCollectionView,
+ Document: this.props.Document,
+ fieldKey: this.props.fieldKey,
+ renderDepth: this.props.renderDepth,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ moveDocument: this.props.moveDocument,
+ setIsEditing: this.setCellIsEditing,
+ isEditable: isEditable,
+ setPreviewDoc: this.props.setPreviewDoc,
+ setComputed: this.setComputed,
+ getField: this.getField,
+ showDoc: this.showDoc,
+ };
+
+ return <CollectionSchemaButtons {...props} />;
+ },
width: 28,
resizable: false
});
return columns;
}
+
+
+ @action
+ nextHighlight = (e: React.MouseEvent, doc: Doc) => {
+ e.preventDefault();
+ e.stopPropagation();
+ doc.searchMatch = false;
+ console.log(doc.searchMatch);
+ setTimeout(() => doc.searchMatch = true, 0);
+ console.log(doc.searchMatch);
+
+ doc.searchIndex = NumCast(doc.searchIndex);
+ }
+
+ @action
+ nextHighlight2 = (doc: Doc) => {
+
+ doc.searchMatchAlt = false;
+ setTimeout(() => doc.searchMatchAlt = true, 0);
+ doc.searchIndex = NumCast(doc.searchIndex);
+ }
+
constructor(props: SchemaTableProps) {
super(props);
// convert old schema columns (list of strings) into new schema columns (list of schema header fields)
@@ -574,7 +623,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return <div className="collectionSchemaView-table" onPointerDown={this.props.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()}
onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
{this.reactTable}
- <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
+ {StrCast(this.props.Document.type) !== "search" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
+ : undefined}
{!this._showDoc ? (null) :
<div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }}
style={{
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index a4fd5384f..b00074cc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -142,9 +142,16 @@ export function computePivotLayout(
const fieldKey = "data";
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
+ let nonNumbers = 0;
const pivotFieldKey = toLabel(pivotDoc._pivotField);
childPairs.map(pair => {
- const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+ const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
+ Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+
+ const num = toNumber(pair.layout[pivotFieldKey]);
+ if (num === undefined || Number.isNaN(num)) {
+ nonNumbers++;
+ }
const val = Field.toString(pair.layout[pivotFieldKey] as Field);
if (lval) {
lval.forEach((val, i) => {
@@ -168,13 +175,6 @@ export function computePivotLayout(
});
}
});
- let nonNumbers = 0;
- childPairs.map(pair => {
- const num = toNumber(pair.layout[pivotFieldKey]);
- if (num === undefined || Number.isNaN(num)) {
- nonNumbers++;
- }
- });
const pivotNumbers = nonNumbers / childPairs.length < .1;
if (pivotColumnGroups.size > 10) {
const arrayofKeys = Array.from(pivotColumnGroups.keys());
@@ -434,27 +434,3 @@ function normalizeResults(
payload: gname.payload
})));
}
-
-export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
- return () => {
- const addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
- const scriptField = Cast(doc[key], ScriptField);
- const scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- onCancel={() => overlayDisposer()} // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
- onSave={(text, onError) => {
- const script = CompileScript(text, { params, requiredType, typecheck: false });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- } else {
- doc[key] = new ScriptField(script);
- overlayDisposer();
- }
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- };
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 92aee3776..2b07c4efb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -13,16 +13,153 @@
}
.collectionfreeformview-viewdef {
- > .collectionFreeFormDocumentView-container {
+ >.collectionFreeFormDocumentView-container {
pointer-events: none;
+
.contentFittingDocumentDocumentView-previewDoc {
pointer-events: all;
}
}
+
+ svg.presPaths {
+ position: absolute;
+ z-index: 100000;
+ overflow: visible;
+ }
+
+ svg.presPaths-hidden {
+ display: none;
+ }
}
.collectionfreeformview-none {
touch-action: none;
+
+ svg.presPaths {
+ position: absolute;
+ z-index: 100000;
+ overflow: visible;
+ }
+
+ svg.presPaths-hidden {
+ display: none;
+ }
+}
+
+.pathOrder {
+ position: absolute;
+ z-index: 200000;
+
+ .pathOrder-frame {
+ position: absolute;
+ width: 40;
+ text-align: center;
+ font-size: 30;
+ background-color: #69a6db;
+ font-family: Roboto;
+ font-weight: 300;
+ }
+}
+
+.progressivizeButton {
+ position: absolute;
+ display: grid;
+ grid-template-columns: auto 20px auto;
+ transform: translate(-105%, 0);
+ align-items: center;
+ border: black solid 1px;
+ border-radius: 3px;
+ justify-content: center;
+ width: 40;
+ z-index: 30000;
+ height: 20;
+ overflow: hidden;
+ background-color: #d5dce2;
+ transition: all 1s;
+
+ .progressivizeButton-prev:hover {
+ color: #5a9edd;
+ }
+
+ .progressivizeButton-frame {
+ justify-self: center;
+ text-align: center;
+ width: 15px;
+ }
+
+ .progressivizeButton-next:hover {
+ color: #5a9edd;
+ }
+}
+
+.resizable {
+ background: rgba(0, 0, 0, 0.2);
+ width: 100px;
+ height: 100px;
+ position: absolute;
+ top: 100px;
+ left: 100px;
+
+ .resizers {
+ width: 100%;
+ height: 100%;
+ border: 3px solid #69a6db;
+ box-sizing: border-box;
+
+ .resizer {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ /*magic to turn square into circle*/
+ background: white;
+ border: 3px solid #69a6db;
+ }
+
+ .resizer.top-left {
+ left: -3px;
+ top: -3px;
+ cursor: nwse-resize;
+ /*resizer cursor*/
+ }
+
+ .resizer.top-right {
+ right: -3px;
+ top: -3px;
+ cursor: nesw-resize;
+ }
+
+ .resizer.bottom-left {
+ left: -3px;
+ bottom: -3px;
+ cursor: nesw-resize;
+ }
+
+ .resizer.bottom-right {
+ right: -3px;
+ bottom: -3px;
+ cursor: nwse-resize;
+ }
+ }
+}
+
+.progressivizeMove-frame {
+ width: 20px;
+ border-radius: 2px;
+ z-index: 100000;
+ color: white;
+ text-align: center;
+ background-color: #5a9edd;
+ transform: translate(-110%, 110%);
+}
+
+.progressivizeButton:hover {
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5);
+
+ .progressivizeButton-frame {
+ background-color: #5a9edd;
+ color: white;
+ }
}
.collectionFreeform-customText {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index daa6e7440..ef4b7b9d2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
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 } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
@@ -46,6 +46,7 @@ import "./CollectionFreeFormView.scss";
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";
@@ -57,7 +58,6 @@ export const panZoomSchema = createSchema({
currentTimecode: "number",
displayTimecode: "number",
currentFrame: "number",
- arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
fitToBox: "boolean",
@@ -77,7 +77,7 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
- scaleField?: string; // used by formattedTextBox when displaying a sidebar freeform view which needs its own scale field
+ scaleField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
};
@@ -214,7 +214,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const layoutDoc = Doc.Layout(d);
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.opacity);
+ CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.opacity);
} else {
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
@@ -892,7 +892,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.focus(doc);
} else {
const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
- const offset = annotOn && (contextHgt / 2 * 96 / 72);
+ const offset = annotOn && (contextHgt / 2);
this.props.Document._scrollY = NumCast(doc.y) - offset;
}
@@ -912,7 +912,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// !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)) {
- if (!doc.z) this.setPan(newPanX, newPanY, "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
+ // 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
}
Doc.BrushDoc(this.props.Document);
this.props.focus(this.props.Document);
@@ -1005,10 +1006,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.props.addDocTab(doc, where);
});
getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
- const result = this.Document.arrangeScript?.script.run(params, console.log);
- if (result?.success) {
- return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
- }
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);
@@ -1149,7 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
componentDidMount() {
super.componentDidMount?.();
- this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation },
+ this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
@@ -1244,13 +1241,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+ !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
!appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
- viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
- viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+
+
+ !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;
!viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
const options = ContextMenu.Instance.findByDescription("Options...");
@@ -1260,7 +1259,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.ContainingCollectionView &&
optionItems.push({ description: "Promote 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" });
- appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+ 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) {
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
@@ -1295,7 +1294,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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.
}
}
@@ -1402,6 +1401,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
<CollectionFreeFormViewPannableContents
centeringShiftX={this.centeringShiftX}
centeringShiftY={this.centeringShiftY}
+ presPaths={BoolCast(this.Document.presPathView)}
+ progressivize={BoolCast(this.Document.editProgressivize)}
+ zoomProgressivize={BoolCast(this.Document.editZoomProgressivize)}
transition={Cast(this.layoutDoc._viewTransition, "string", null)}
viewDefDivClick={this.props.viewDefDivClick}
zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
@@ -1485,11 +1487,51 @@ interface CollectionFreeFormViewPannableContentsProps {
viewDefDivClick?: ScriptField;
children: () => JSX.Element[];
transition?: string;
+ presPaths?: boolean;
+ progressivize?: boolean;
+ zoomProgressivize?: boolean;
}
@observer
class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
+ @computed get zoomProgressivize() {
+ return PresBox.Instance && this.props.zoomProgressivize ? PresBox.Instance.zoomProgressivizeContainer : (null);
+ }
+
+ @computed get progressivize() {
+ return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null);
+ }
+
+ @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></>}
+ </>);
+ }
+
render() {
+ // trace();
const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none");
const cenx = this.props.centeringShiftX();
const ceny = this.props.centeringShiftY();
@@ -1502,6 +1544,9 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
transition: this.props.transition
}}>
{this.props.children()}
+ {this.presPaths}
+ {this.progressivize}
+ {this.zoomProgressivize}
</div>;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
index 88876471c..d49ab27fb 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -24,14 +24,18 @@
.formatShapePane-inputBtn {
width: inherit;
- position: absolute;
+ position: absolute;
}
-.sketch-picker {
- background: #323232;
-
- .flexbox-fit {
+.btn-group-palette {
+ .sketch-picker {
background: #323232;
+ width: 160px !important;
+ height: 80% !important;
+
+ .flexbox-fit {
+ background: #323232;
+ }
}
}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index 4e328d838..6263be261 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -12,6 +12,7 @@ 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 {
@@ -20,14 +21,16 @@ export default class FormatShapePane extends AntimodeMenu {
private _lastFill = "#D0021B";
private _lastLine = "#D0021B";
private _lastDash = "2";
- private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF"];
private _mode = ["fill-drip", "ruler-combined"];
- @observable private _subOpen = [false, false, false, false];
+ @observable private _subOpen = [false, false];
@observable private _currMode = "fill-drip";
- @observable private _lock = false;
+ @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) =>
@@ -101,19 +104,58 @@ export default class FormatShapePane extends AntimodeMenu {
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);
- i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
+ 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;
}
@@ -121,12 +163,11 @@ export default class FormatShapePane extends AntimodeMenu {
@undoBatch
@action
- rotate = (degrees: number) => {
- this.selectedInk?.forEach(action(inkView => {
+ 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 angle = Number(degrees) - Number(doc.rotation);
- doc.rotation = Number(degrees);
const ink = Cast(doc.data, InkField)?.inkData;
if (ink) {
const xs = ink.map(p => p.X);
@@ -135,143 +176,280 @@ export default class FormatShapePane extends AntimodeMenu {
const top = Math.min(...ys);
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- const _centerPoints: { X: number, Y: number }[] = [];
_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 }[] = [];
- for (var i = 0; i < ink.length; i++) {
- const newX = Math.cos(angle) * (ink[i].X - _centerPoints[0].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].X;
- const newY = Math.sin(angle) * (ink[i].X - _centerPoints[0].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].Y;
+ 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 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) * inkView.props.ScreenToLocalTransform().Scale;
- doc._width = (right2 - left2) * inkView.props.ScreenToLocalTransform().Scale;
+ 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) {
- colorPicker(setter: (color: string) => {}) {
- return <div className="btn-group-palette" key="colorpicker" >
- {this._palette.map(color =>
- <button className="antimodeMenu-button" key={color} onPointerDown={undoBatch(action(() => setter(color)))} style={{ zIndex: 1001, position: "relative" }}>
- <div className="color-previewII" style={{ backgroundColor: color }} />
- </button>)}
+ 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: 80, position: "absolute", right: 20 }}
+ <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
type="text" value={value}
- onChange={e => setter(e.target.value)}
+ onChange={undoBatch(action((e) => setter(e.target.value)))}
autoFocus />
- <button className="antiMenu-Buttonup" key="up" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
+ <button className="antiMenu-Buttonup" key="up1" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
˄
</button>
<br />
- <button className="antiMenu-Buttonup" key="down" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
+ <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="fill" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "absolute", right: 80 }}>
- <FontAwesomeIcon icon="fill-drip" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} />
+ <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 />
</>;
}
- @computed get fillButton() { return this.colorButton(this.colorFil, () => this._fillBtn = !this._fillBtn); }
- @computed get lineButton() { return this.colorButton(this.colorStk, () => this._lineBtn = !this._lineBtn); }
+ 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" /> */}
+ ⟲
- @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); }
- @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); }
+ </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 hgtInput() { return this.inputBox("hgt", this.shapeHgt, (val: string) => this.shapeHgt = 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.inputBox("rot", this.shapeRot, (val: string) => this.shapeRot = val); }
- @computed get XpsInput() { return this.inputBox("Xps", this.shapeXps, (val: string) => this.shapeXps = 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 propertyGroupItems() {
- const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} />
- No Fill
- <br />
- <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={undoBatch(action(() => this.solidFil = true))} />
- Solid Fill
- <br /> <br />
- {this.solidFil ? "Color" : ""}
- {this.solidFil ? this.fillButton : ""}
- {this._fillBtn && this.solidFil ? this.fillPicker : ""}
- </div>;
+ @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:"); }
- const markers = <>
- <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
- Arrow Head
- <br />
- <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
- Arrow End
- <br />
- </>;
- const lineCheck = <div key="lineCheck" style={{ display: this._subOpen[1] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={undoBatch(action(() => this.unStrokd = true))} />
- No Line
- <br />
- <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={undoBatch(action(() => this.solidStk = true))} />
- Solid Line
- <br />
- <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={undoBatch(action(() => this.dashdStk = "2"))} />
- Dash Line
- <br />
- <br />
- {(this.solidStk || this.dashdStk) ? "Color" : ""}
- {(this.solidStk || this.dashdStk) ? this.lineButton : ""}
- {(this.solidStk || this.dashdStk) && this._lineBtn ? this.linePicker : ""}
- <br />
+ @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={e => this.widthStk = e.target.value} /> : (null)}
- <br /> <br />
- {(this.solidStk || this.dashdStk) ? markers : ""}
- </div>;
- const sizeCheck = <div key="sizeCheck" style={{ display: this._subOpen[2] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- Height {this.hgtInput}
- <br /> <br />
- Width {this.widInput}
- <br /> <br />
- <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={undoBatch(action(() => this._lock = !this._lock))} />
- Lock Ratio
- <br /> <br />
- Rotation {this.rotInput}
- <br /> <br />
- </div>;
- const positionCheck = <div key="posCheck" style={{ display: this._subOpen[3] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- Horizontal {this.XpsInput}
- <br /> <br />
- Vertical {this.YpsInput}
- <br /> <br />
+ {(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 subMenus = this._currMode === "fill-drip" ? [`fill`, `line`] : [`size`, `position`];
- const menuItems = this._currMode === "fill-drip" ? [fillCheck, lineCheck] : [sizeCheck, positionCheck];
- const indexOffset = this._currMode === "fill-drip" ? 0 : 2;
+
+
+ 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" }}>
@@ -302,6 +480,7 @@ export default class FormatShapePane extends AntimodeMenu {
}
render() {
- return this.getElementVert([this.closeBtn, this.propertyGroupBtn, this.propertyGroupItems]);
+ return this.getElementVert([this.closeBtn,
+ this.propertyGroupItems]);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 764758eee..88fe03efd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
-import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
+import { GetEffectiveAcl } from "../../../../fields/util";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -281,7 +281,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = x;
this._downY = y;
const effectiveAcl = GetEffectiveAcl(this.props.Document);
- if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl) || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
this.clearSelection();
}
});
@@ -339,10 +339,21 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._visible = false;
}
+ @undoBatch
@action
delete = () => {
- this.props.removeDocument(this.marqueeSelect(false));
+ const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
+ const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
+
+ selected.map(doc => {
+ const effectiveAcl = GetEffectiveAcl(doc);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete
+ recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
+ this.props.removeDocument(doc);
+ }
+ });
+
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
new file mode 100644
index 000000000..5e0c9fcbb
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
@@ -0,0 +1,733 @@
+.propertiesView {
+
+ background-color: rgb(205, 205, 205);
+ height: 100%;
+ font-family: "Noto Sans";
+ cursor: auto;
+
+ overflow-x: hidden;
+ overflow-y: scroll;
+
+ .propertiesView-title {
+ background-color: rgb(159, 159, 159);
+ text-align: center;
+ padding-top: 12px;
+ padding-bottom: 12px;
+ display: flex;
+ font-size: 18px;
+ font-weight: bold;
+ justify-content: center;
+
+ .propertiesView-title-icon {
+ width: 20px;
+ height: 20px;
+ padding-left: 38px;
+ margin-top: -5px;
+ right: 19;
+ position: absolute;
+
+ &:hover {
+ color: grey;
+ cursor: pointer;
+ }
+
+ }
+
+ }
+
+ .propertiesView-name {
+ border-bottom: 1px solid black;
+ padding: 8.5px;
+ font-size: 12.5px;
+
+ &:hover {
+ cursor: text;
+ }
+ }
+
+ .propertiesView-settings {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+ font-size: 12.5px;
+ font-weight: bold;
+
+ .propertiesView-settings-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-settings-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-settings-content {
+ margin-left: 12px;
+ padding-bottom: 10px;
+ padding-top: 8px;
+ }
+
+ }
+
+ .propertiesView-sharing {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-sharing-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-sharing-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-sharing-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+
+ .change-buttons {
+ display: flex;
+
+ button {
+ width: 5;
+ height: 5;
+ }
+
+ input {
+ width: 100%;
+ }
+ }
+ }
+ }
+
+ .propertiesView-appearance {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-appearance-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-appearance-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-appearance-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+ }
+ }
+
+ .propertiesView-transform {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-transform-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-transform-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-transform-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+ }
+ }
+
+ .notify-button {
+ padding: 2px;
+ width: 12px;
+ height: 12px;
+ background-color: black;
+ border-radius: 10px;
+ padding-left: 2px;
+ padding-right: 2px;
+ margin-top: 2px;
+ margin-left: 3px;
+
+ .notify-button-icon {
+ width: 6px;
+ height: 6.5px;
+ margin-left: .5px;
+ }
+
+ &:hover {
+ background-color: rgb(158, 158, 158);
+ cursor: pointer;
+ }
+ }
+
+ .expansion-button-icon {
+ width: 11px;
+ height: 11px;
+ color: black;
+ margin-left: 27px;
+
+ &:hover {
+ color: rgb(131, 131, 131);
+ cursor: pointer;
+ }
+ }
+
+ .propertiesView-sharingTable {
+
+ // whatever's commented out - add it back in when adding the buttons
+
+ // border: 1.5px solid black;
+ border: 1px solid black;
+ padding: 5px; // remove when adding buttons
+ border-radius: 6px; // remove when adding buttons
+ margin-right: 10px; // remove when adding buttons
+ // width: 100%;
+ // display: inline-table;
+ background-color: #ececec;
+ max-height: 130px;
+ overflow-y: scroll;
+
+ .propertiesView-sharingTable-item {
+
+ display: flex;
+ // padding: 5px;
+ padding: 3px;
+ align-items: center;
+ border-bottom: 0.5px solid grey;
+ cursor: pointer;
+
+ &:hover .propertiesView-sharingTable-item-name {
+ overflow-x: unset;
+ white-space: unset;
+ overflow-wrap: break-word;
+ }
+
+ .propertiesView-sharingTable-item-name {
+ font-weight: bold;
+ width: 95px;
+ overflow-x: hidden;
+ display: inline-block;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .propertiesView-sharingTable-item-permission {
+ display: flex;
+ right: 34;
+ float: right;
+ position: absolute;
+
+ .permissions-select {
+ z-index: 1;
+ border: none;
+ background-color: inherit;
+ width: 75px;
+ //text-align: justify; // for Edge
+ //text-align-last: end;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+ }
+
+ .propertiesView-fields {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-fields-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-fields-title-name {
+ font-size: 12.5px;
+ font-weight: bold;
+ white-space: nowrap;
+ width: 35px;
+ display: flex;
+ }
+
+ .propertiesView-fields-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-fields-checkbox {
+ float: right;
+ height: 20px;
+ margin-top: -9px;
+
+ .propertiesView-fields-checkbox-text {
+ font-size: 7px;
+ margin-top: -10px;
+ margin-left: 6px;
+ }
+ }
+
+ .propertiesView-fields-content {
+ font-size: 10px;
+ margin-left: 2px;
+ padding: 10px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .field {
+ display: flex;
+ font-size: 7px;
+ background-color: #e8e8e8;
+ padding-right: 3px;
+ border: 0.5px solid grey;
+ border-radius: 5px;
+ padding-left: 3px;
+ }
+
+ .uneditable-field {
+ display: flex;
+ overflow-y: visible;
+ margin-bottom: 2px;
+
+ &:hover {
+ cursor: auto;
+ }
+ }
+
+ .propertiesView-layout {
+
+ .propertiesView-layout-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-layout-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-layout-content {
+ overflow: hidden;
+ padding: 10px;
+ }
+
+ }
+
+ .propertiesView-presTrails {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-presTrails-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-presTrails-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-presTrails-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+ }
+ }
+}
+
+.inking-button {
+
+ display: flex;
+
+ .inking-button-points {
+ background-color: #333333;
+ padding: 7px;
+ border-radius: 7px;
+ margin-right: 32px;
+ width: 32;
+ height: 32;
+ padding-top: 9px;
+ margin-left: 18px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ .inking-button-lock {
+ background-color: #333333;
+ padding: 7px;
+ border-radius: 7px;
+ margin-right: 32px;
+ width: 32;
+ height: 32;
+ padding-top: 9px;
+ padding-left: 10px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ .inking-button-rotate {
+ background-color: #333333;
+ padding: 7px;
+ border-radius: 7px;
+ width: 32;
+ height: 32;
+ padding-top: 9px;
+ padding-left: 10px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+}
+
+.inputBox-duo {
+ display: flex;
+}
+
+.inputBox {
+
+ margin-top: 10px;
+ display: flex;
+ height: 19px;
+ margin-right: 15px;
+
+ .inputBox-title {
+ font-size: 12px;
+ padding-right: 5px;
+ }
+
+ .inputBox-input {
+ font-size: 10px;
+ width: 50px;
+ margin-right: 1px;
+ border-radius: 3px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .inputBox-button {
+
+ .inputBox-button-up {
+ background-color: #333333;
+ height: 9px;
+ padding-left: 3px;
+ padding-right: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ border-radius: 1.5px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ .inputBox-button-down {
+ background-color: #333333;
+ height: 9px;
+ padding-left: 3px;
+ padding-right: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ border-radius: 1.5px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ }
+}
+
+.color-palette {
+ width: 160px;
+ height: 360;
+}
+
+.strokeAndFill {
+ display: flex;
+ margin-top: 10px;
+
+ .fill {
+ margin-right: 40px;
+ display: flex;
+ padding-bottom: 7px;
+ margin-left: 35px;
+
+ .fill-title {
+ font-size: 12px;
+ margin-right: 2px;
+ }
+
+ .fill-button {
+ padding-top: 2px;
+ margin-top: -1px;
+ }
+ }
+
+ .stroke {
+ display: flex;
+
+ .stroke-title {
+ font-size: 12px;
+ }
+
+ .stroke-button {
+ padding-top: 2px;
+ margin-left: 2px;
+ margin-top: -1px;
+ }
+ }
+}
+
+.propertiesView-presSelected {
+ border-top: solid 1px darkgrey;
+ width: 100%;
+ padding-top: 5px;
+ font-family: Roboto;
+ font-weight: 500;
+ display: inline-flex;
+
+ .propertiesView-selectedList {
+ border-left: solid 1px darkgrey;
+ margin-left: 10px;
+ padding-left: 5px;
+
+ .selectedList-items {
+ font-size: 12;
+ font-weight: 300;
+ margin-top: 1;
+ }
+ }
+}
+
+.widthAndDash {
+
+ .width {
+ .width-top {
+ display: flex;
+
+ .width-title {
+ font-size: 12;
+ margin-right: 20px;
+ margin-left: 35px;
+ text-align: center;
+ }
+
+ .width-input {
+ margin-right: 30px;
+ margin-top: -10px;
+ }
+ }
+
+ .width-range {
+ margin-right: 1px;
+ margin-bottom: 6;
+ }
+ }
+
+ .arrows {
+
+ display: flex;
+ margin-bottom: 3px;
+ margin-left: 4px;
+
+ .arrows-head {
+
+ display: flex;
+ margin-right: 35px;
+
+ .arrows-head-title {
+ font-size: 10;
+ }
+
+ .arrows-head-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+
+ .arrows-tail {
+ display: flex;
+
+ .arrows-tail-title {
+ font-size: 10;
+ }
+
+ .arrows-tail-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+ }
+
+ .dashed {
+
+ display: flex;
+ margin-left: 64px;
+ margin-bottom: 6px;
+
+ .dashed-title {
+ font-size: 10;
+ }
+
+ .dashed-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+}
+
+.editable-title {
+ border: none;
+ padding: 6px;
+ padding-bottom: 2px;
+
+
+ &:hover {
+ border: 0.75px solid rgb(122, 28, 28);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
new file mode 100644
index 000000000..1a8ee3ea1
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -0,0 +1,1037 @@
+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 { ColorState, SketchPicker } from "react-color";
+import "./FormatShapePane.scss";
+import { discovery_v1 } from "googleapis";
+import { PresBox } from "../../nodes/PresBox";
+import { DocumentManager } from "../../../util/DocumentManager";
+import FormatShapePane from "./FormatShapePane";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+// 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
+export class PropertiesView extends React.Component<PropertiesViewProps> {
+ private _widthUndo?: UndoManager.Batch;
+
+ @computed get MAX_EMBED_HEIGHT() { return 200; }
+
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else if (PresBox.Instance._selectedArray.length) {
+ return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+ } else { 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; }
+
+ @observable layoutFields: boolean = false;
+
+ @observable openActions: boolean = true;
+ @observable openSharing: boolean = true;
+ @observable openFields: boolean = true;
+ @observable openLayout: boolean = true;
+ @observable openAppearance: boolean = true;
+ @observable openTransform: boolean = true;
+ // @observable selectedUser: string = "";
+ // @observable addButtonPressed: boolean = false;
+
+ //Pres Trails booleans:
+ @observable openPresTransitions: boolean = false;
+ @observable openPresProgressivize: boolean = false;
+ @observable openAddSlide: boolean = false;
+ @observable openSlideOptions: boolean = false;
+
+ @observable inActions: boolean = false;
+ @observable _controlBtn: boolean = false;
+ @observable _lock: boolean = false;
+
+ @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
+
+ @action
+ rtfWidth = () => {
+ if (this.selectedDoc) {
+ return Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
+ } else {
+ return 0;
+ }
+ }
+ @action
+ rtfHeight = () => {
+ if (this.selectedDoc) {
+ return this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ } else {
+ return 0;
+ }
+ }
+
+ @action
+ docWidth = () => {
+ if (this.selectedDoc) {
+ const layoutDoc = this.selectedDoc;
+ const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
+ if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.width - 20));
+ return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20;
+ } else {
+ return 0;
+ }
+ }
+
+ @action
+ docHeight = () => {
+ if (this.selectedDoc && this.dataDoc) {
+ const layoutDoc = this.selectedDoc;
+ return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
+ const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
+ if (aspect) return this.docWidth() * aspect;
+ return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) :
+ Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
+ NumCast(this.props.height)))) :
+ NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
+ })()));
+ } else {
+ return 0;
+ }
+ }
+
+ @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 rows: JSX.Element[] = [];
+ for (const key of Object.keys(ids).slice().sort()) {
+ const contents = doc[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"
+ 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>);
+ }
+ }
+ rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
+ <EditableView
+ key="editableView"
+ contents={"add key:value or #tags"}
+ height={13}
+ fontSize={10}
+ GetValue={() => ""}
+ SetValue={this.setKeyValue} />
+ </div>);
+ return rows;
+ }
+ }
+
+ @computed get noviceFields() {
+ if (this.dataDoc && this.selectedDoc) {
+ 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 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] === "#") {
+ rows.push(<div className="uneditable-field" key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
+ &nbsp;
+ </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>);
+ }
+ }
+ }
+ }
+ rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
+ <EditableView
+ key="editableView"
+ contents={"add key:value or #tags"}
+ height={13}
+ fontSize={10}
+ GetValue={() => ""}
+ SetValue={this.setKeyValue} />
+ </div>);
+ return rows;
+ }
+ }
+
+ @undoBatch
+ setKeyValue = (value: string) => {
+ if (this.selectedDoc && this.dataDoc) {
+ const doc = this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc;
+ 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);
+ 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);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @computed get layoutPreview() {
+ if (this.selectedDoc) {
+ const layoutDoc = Doc.Layout(this.selectedDoc);
+ const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
+ const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth;
+ return <div style={{ display: "inline-block", height: panelHeight() }} key={this.selectedDoc[Id]}>
+ <ContentFittingDocumentView
+ Document={layoutDoc}
+ DataDoc={this.dataDoc}
+ LibraryPath={emptyPath}
+ renderDepth={this.props.renderDepth + 1}
+ rootSelected={returnFalse}
+ treeViewDoc={undefined}
+ backgroundColor={() => "lightgrey"}
+ fitToBox={true}
+ FreezeDimensions={true}
+ NativeWidth={layoutDoc.type ===
+ StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero}
+ NativeHeight={layoutDoc.type ===
+ StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : returnZero}
+ PanelWidth={panelWidth}
+ PanelHeight={panelHeight}
+ focus={returnFalse}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ addDocument={returnFalse}
+ moveDocument={undefined}
+ removeDocument={returnFalse}
+ parentActive={() => false}
+ whenActiveChanged={emptyFunction}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
+ dontRegisterView={true}
+ dropAction={undefined}
+ />
+ </div>;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Handles the changing of a user's permissions from the permissions panel.
+ */
+ @undoBatch
+ changePermissions = (e: any, user: string) => {
+ SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!);
+ }
+
+ /**
+ * @returns the options for the permissions dropdown.
+ */
+ getPermissionsSelect(user: string, permission: string) {
+ return <select className="permissions-select"
+ defaultValue={permission}
+ onChange={e => this.changePermissions(e, user)}>
+ {Object.values(SharingPermissions).map(permission => {
+ return (
+ <option key={permission} value={permission} selected={this.selectedDoc![`ACL-${user.replace(".", "_")}`] === permission}>
+ {permission}
+ </option>);
+ })}
+ </select>;
+ }
+
+ /**
+ * @returns the notification icon. On clicking, it should notify someone of a document been shared with them.
+ */
+ @computed get notifyIcon() {
+ return <Tooltip title={<><div className="dash-tooltip">Notify with message</div></>}>
+ <div className="notify-button">
+ <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" />
+ </div>
+ </Tooltip>;
+ }
+
+ /**
+ * ... next to the owner that opens the main SharingManager interface on click.
+ */
+ @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);
+ }
+ }}>
+ <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
+ </div>
+ </Tooltip>;
+ }
+
+ /**
+ * @returns a row of the permissions panel
+ */
+ sharingItem(name: string, effectiveAcl: symbol, permission: string) {
+ return <div className="propertiesView-sharingTable-item"
+ // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
+ // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
+ >
+ <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}
+ {permission === "Owner" ? this.expansionIcon : null}
+ </div>
+ </div>;
+ }
+
+ /**
+ * @returns the sharing and permissiosn panel.
+ */
+ @computed get sharingTable() {
+ const AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit],
+ [AclAdmin, SharingPermissions.Admin]
+ ]);
+
+ const effectiveAcl = GetEffectiveAcl(this.selectedDoc!);
+ const tableEntries = [];
+
+ // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
+ if (this.selectedDoc![AclSym]) {
+ for (const [key, value] of Object.entries(this.selectedDoc![AclSym])) {
+ 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)!));
+ }
+ }
+
+ // if (Doc.UserDoc().sidebarUsersDisplayed) {
+ // for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) {
+ // if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None));
+ // }
+ // }
+ // })
+
+ // 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"));
+
+ return <div className="propertiesView-sharingTable">
+ {tableEntries}
+ </div>;
+ }
+
+ @computed get fieldsCheckbox() {
+ return <Checkbox
+ color="primary"
+ onChange={this.toggleCheckbox}
+ checked={this.layoutFields}
+ />;
+ }
+
+ @action
+ toggleCheckbox = () => {
+ this.layoutFields = !this.layoutFields;
+ }
+
+ @computed get editableTitle() {
+ return <div className="editable-title"><EditableView
+ key="editableView"
+ contents={StrCast(this.selectedDoc?.title)}
+ height={25}
+ fontSize={14}
+ GetValue={() => StrCast(this.selectedDoc?.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);
+ return true;
+ }
+ return false;
+ }
+
+
+ @undoBatch
+ @action
+ rotate = (angle: number) => {
+ const _centerPoints: { X: number, Y: number }[] = [];
+ if (this.selectedDoc) {
+ const doc = this.selectedDoc;
+ 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;
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ doc.rotation = Number(doc.rotation) + Number(angle);
+ const inks = Cast(doc.data, InkField)?.inkData;
+ if (inks) {
+ const newPoints: { X: number, Y: number }[] = [];
+ inks.forEach(ink => {
+ const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X;
+ const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.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++;
+ }
+ }
+ }
+
+
+
+ @computed
+ get controlPointsButton() {
+ return <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" : "" }}>
+ <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" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Rotate 90˚"}</div></>}>
+ <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
+ <FontAwesomeIcon icon="undo" color="white" size="lg" />
+ </div>
+ </Tooltip>
+ </div>;
+ }
+
+ inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
+ return <div className="inputBox"
+ style={{
+ marginRight: title === "X:" ? "19px" : "",
+ marginLeft: title === "∠:" ? "39px" : ""
+ }}>
+ <div className="inputBox-title"> {title} </div>
+ <input className="inputBox-input"
+ type="text" value={value}
+ onChange={e => setter(e.target.value)} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
+ <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
+ <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ </div>
+ </div>
+ </div>;
+ }
+
+ inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
+ return <div className="inputBox-duo">
+ {this.inputBox(key, value, setter, title1)}
+ {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)}
+ </div>;
+ }
+
+ @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.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break;
+ case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break;
+ case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
+ case "wid":
+ const oldWidth = NumCast(this.selectedDoc?._width);
+ const oldHeight = NumCast(this.selectedDoc?._height);
+ 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)));
+ const doc = this.selectedDoc;
+ if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth;
+ const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+ }
+ }
+ break;
+ case "hgt":
+ const oWidth = NumCast(this.selectedDoc?._width);
+ const oHeight = NumCast(this.selectedDoc?._height);
+ 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)));
+ const docu = this.selectedDoc;
+ if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
+ const ink = Cast(docu.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth;
+ const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ docu.data = new InkField(newPoints);
+ }
+ }
+ break;
+ }
+ }
+
+ getField(key: string) {
+ //if (this.selectedDoc) {
+ return Field.toString(this.selectedDoc?.[key] as Field);
+ // } else {
+ // return undefined as Opt<string>;
+ // }
+ }
+
+ @computed get shapeXps() { return this.getField("x"); }
+ @computed get shapeYps() { return this.getField("y"); }
+ @computed get shapeRot() { return this.getField("rotation"); }
+ @computed get shapeHgt() { return this.getField("_height"); }
+ @computed get shapeWid() { return this.getField("_width"); }
+ set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); }
+ set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); }
+ set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); }
+ 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);
+ }
+ 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);
+ }
+
+ @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:"); }
+ @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); }
+ @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); }
+
+
+ @observable private _fillBtn = false;
+ @observable private _lineBtn = false;
+
+ private _lastFill = "#D0021B";
+ private _lastLine = "#D0021B";
+ private _lastDash: any = "2";
+
+ @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; }
+ set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); }
+ set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); }
+
+ colorButton(value: string, type: string, setter: () => {}) {
+ return <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={type === "fill" ? this.fillPicker : this.linePicker}>
+ <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}>
+ <div className="color-button-preview" style={{
+ backgroundColor: value ?? "121212", width: 15, height: 15,
+ display: value === "" || value === "transparent" ? "none" : ""
+ }} />
+ {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14, position: "fixed" }}>☒</p> : ""}
+ </div>
+ </Flyout>;
+
+ }
+
+ @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 <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} />;
+ }
+
+ @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
+ @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { 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 strokeAndFill() {
+ return <div>
+ <div key="fill" className="strokeAndFill">
+ <div className="fill">
+ <div className="fill-title">Fill:</div>
+ <div className="fill-button">{this.fillButton}</div>
+ </div>
+ <div className="stroke">
+ <div className="stroke-title"> Stroke: </div>
+ <div className="stroke-button">{this.lineButton}</div>
+ </div>
+ </div>
+ {/* {this._fillBtn ? this.fillPicker : ""}
+ {this._lineBtn ? this.linePicker : ""} */}
+ </div>;
+ }
+
+ @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; }
+ @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; }
+ @computed get unStrokd() { return this.selectedDoc?.color ? true : false; }
+ @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+ @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+ @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+ set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ set dashdStk(value) {
+ value && (this._lastDash = value) && (this.unStrokd = false);
+ this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined);
+ }
+ set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); }
+ set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+ set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); }
+ set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); }
+
+
+ @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); }
+
+
+ regInput = (key: string, value: any, setter: (val: string) => {}) => {
+ return <div className="inputBox">
+ <input className="inputBox-input"
+ type="text" value={value}
+ onChange={e => setter(e.target.value)} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
+ <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
+ <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ </div>
+ </div>
+ </div>;
+ }
+
+ @computed get widthAndDash() {
+ return <div className="widthAndDash">
+ <div className="width">
+ <div className="width-top">
+ <div className="width-title">Width:</div>
+ <div className="width-input">{this.stkInput}</div>
+ </div>
+ <input className="width-range" type="range"
+ defaultValue={Number(this.widthStk)} min={1} max={100}
+ onChange={(action((e) => this.widthStk = e.target.value))}
+ onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }}
+ onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }}
+ />
+ </div>
+
+ <div className="arrows">
+ <div className="arrows-head">
+ <div className="arrows-head-title" >Arrow Head: </div>
+ <input key="markHead" className="arrows-head-input" type="checkbox"
+ checked={this.markHead !== ""}
+ onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
+ </div>
+ <div className="arrows-tail">
+ <div className="arrows-tail-title" >Arrow End: </div>
+ <input key="markTail" className="arrows-tail-input" type="checkbox"
+ checked={this.markTail !== ""}
+ onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
+ </div>
+ </div>
+ <div className="dashed">
+ <div className="dashed-title">Dashed Line:</div>
+ <input key="markHead" className="dashed-input"
+ type="checkbox" checked={this.dashdStk === "2"}
+ onChange={this.changeDash} />
+ </div>
+ </div>;
+ }
+
+ @undoBatch @action
+ changeDash = () => {
+ this.dashdStk = this.dashdStk === "2" ? "0" : "2";
+ }
+
+ @computed get appearanceEditor() {
+ return <div className="appearance-editor">
+ {this.widthAndDash}
+ {this.strokeAndFill}
+ </div>;
+ }
+
+ @computed get transformEditor() {
+ return <div className="transform-editor">
+ {this.controlPointsButton}
+ {this.hgtInput}
+ {this.XpsInput}
+ {this.rotInput}
+ </div>;
+ }
+
+ /**
+ * Handles adding and removing members from the sharing panel
+ */
+ // handleUserChange = (selectedUser: string, add: boolean) => {
+ // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc;
+ // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
+ // sidebarUsersDisplayed![`display-${selectedUser}`] = add;
+ // !add && runInAction(() => this.selectedUser = "");
+ // });
+ // }
+
+ render() {
+ if (!this.selectedDoc && !this.isPres) {
+ return <div className="propertiesView" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ No Document Selected
+ </div>
+ </div>;
+
+ } else {
+ const novice = Doc.UserDoc().noviceMode;
+
+ if (this.selectedDoc && !this.isPres) {
+ return <div className="propertiesView" style={{
+ width: this.props.width,
+ // overflowY: this.inActions ? "visible" : "scroll"
+ }} >
+ <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-title"
+ onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })}
+ style={{ backgroundColor: this.openActions ? "black" : "" }}>
+ Actions
+ <div className="propertiesView-settings-title-icon">
+ <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openActions ? (null) :
+ <div className="propertiesView-settings-content">
+ <PropertiesButtons />
+ </div>}
+ </div>
+ <div className="propertiesView-sharing">
+ <div className="propertiesView-sharing-title"
+ onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })}
+ style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+ Sharing {"&"} Permissions
+ <div className="propertiesView-sharing-title-icon">
+ <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openSharing ? (null) :
+ <div className="propertiesView-sharing-content">
+ {this.sharingTable}
+ {/* <div className="change-buttons">
+ <button
+ onPointerDown={action(() => this.addButtonPressed = !this.addButtonPressed)}
+ >
+ <FontAwesomeIcon icon={fa.faPlus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} />
+ </button>
+ <button
+ id="sharingProperties-removeUser"
+ onPointerDown={() => this.handleUserChange(this.selectedUser, false)}
+ style={{ backgroundColor: this.selectedUser ? "#121721" : "#777777" }}
+ ><FontAwesomeIcon icon={fa.faMinus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
+ <button onClick={() => SharingManager.Instance.open(this.selectedDocumentView!)}><FontAwesomeIcon icon={fa.faCog} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
+ {this.addButtonPressed ?
+ // <input type="text" onKeyDown={this.handleKeyPress} /> :
+ <select onChange={e => this.handleUserChange(e.target.value, true)}>
+ <option selected disabled hidden>
+ Add users
+ </option>
+ {SharingManager.Instance.users.map(user =>
+ (<option value={user.user.email}>
+ {user.user.email}
+ </option>)
+ )}
+ {GroupManager.Instance.getAllGroups().map(group =>
+ (<option value={StrCast(group.groupName)}>
+ {StrCast(group.groupName)}
+ </option>))}
+ </select> :
+ null}
+ </div> */}
+ </div>}
+ </div>
+
+ {!this.isInk ? (null) :
+ <div className="propertiesView-appearance">
+ <div className="propertiesView-appearance-title"
+ onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })}
+ style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
+ Appearance
+ <div className="propertiesView-appearance-title-icon">
+ <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openAppearance ? (null) :
+ <div className="propertiesView-appearance-content">
+ {this.appearanceEditor}
+ </div>}
+ </div>}
+
+ {this.isInk ? <div className="propertiesView-transform">
+ <div className="propertiesView-transform-title"
+ onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })}
+ style={{ backgroundColor: this.openTransform ? "black" : "" }}>
+ Transform
+ <div className="propertiesView-transform-title-icon">
+ <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openTransform ? <div className="propertiesView-transform-content">
+ {this.transformEditor}
+ </div> : null}
+ </div> : null}
+
+ <div className="propertiesView-fields">
+ <div className="propertiesView-fields-title"
+ onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })}
+ style={{ backgroundColor: this.openFields ? "black" : "" }}>
+ <div className="propertiesView-fields-title-name">
+ Fields {"&"} Tags
+ <div className="propertiesView-fields-title-icon">
+ <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ </div>
+ {!novice && this.openFields ? <div className="propertiesView-fields-checkbox">
+ {this.fieldsCheckbox}
+ <div className="propertiesView-fields-checkbox-text">Layout</div>
+ </div> : null}
+ {!this.openFields ? (null) :
+ <div className="propertiesView-fields-content">
+ {novice ? this.noviceFields : this.expandedField}
+ </div>}
+ </div>
+ <div className="propertiesView-layout">
+ <div className="propertiesView-layout-title"
+ onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}
+ style={{ backgroundColor: this.openLayout ? "black" : "" }}>
+ Layout
+ <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}>
+ <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null}
+ </div>
+ </div>;
+ }
+ if (this.isPres) {
+ const selectedItem: boolean = PresBox.Instance._selectedArray.length > 0;
+ return <div className="propertiesView" style={{ width: this.props.width }} >
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ Presentation
+ <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 className="propertiesView-presSelected">
+ {PresBox.Instance?._selectedArray.length} selected
+ <div className="propertiesView-selectedList">
+ {PresBox.Instance?.listOfSelected}
+ </div>
+ </div>
+ </div>
+ {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })}
+ style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"rocket"} /> &nbsp; Transitions
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresTransitions ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.transitionDropdown}
+ </div> : null}
+ </div>}
+ {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })}
+ style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"tasks"} /> &nbsp; Progressivize
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.progressivizeDropdown}
+ </div> : null}
+ </div>}
+ {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })}
+ style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"cog"} /> &nbsp; {PresBox.Instance.stringType} options
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openSlideOptions ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.optionsDropdown}
+ </div> : null}
+ </div>}
+ <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })}
+ style={{ backgroundColor: this.openAddSlide ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"plus"} /> &nbsp; Add new slide
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openAddSlide ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.newDocumentDropdown}
+ </div> : null}
+ </div>
+ <div className="propertiesView-sharing">
+ <div className="propertiesView-sharing-title"
+ onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })}
+ style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+ Sharing {"&"} Permissions
+ <div className="propertiesView-sharing-title-icon">
+ <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openSharing ? <div className="propertiesView-sharing-content">
+ {this.sharingTable}
+ </div> : null}
+ </div>
+ </div>;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 21f77e47b..e6ac7021a 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -240,7 +240,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
w: Math.min(w, this.numCols), // reduces width if greater than numCols
static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu
})) :
- this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index)));
+ this.savedLayoutList.map((layout, index) => { Object.assign(layout, this.unflexedPosition(index)); return layout; });
}
/**
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 3e54d001b..4c79a7c2f 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -9,10 +9,10 @@ $main-accent: #aaaaa3;
//$alt-accent: #59dff7;
$alt-accent: #c2c2c5;
$lighter-alt-accent: rgb(207, 220, 240);
-$darker-alt-accent: rgb(178, 206, 248);
+$darker-alt-accent: #b2cef8;
$intermediate-color: #9c9396;
$dark-color: #121721;
-$link-color: lightBlue;
+$link-color: #add8e6;
$antimodemenu-height: 35px;
// fonts
$sans-serif: "Noto Sans",
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index d26b7920a..7e6999cdc 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -89,7 +89,7 @@
/* float: right; */
border-radius: 7px;
font-size: 9px;
- background-color: black;
+ background: black;
/* padding: 3px; */
padding-top: 4px;
padding-left: 7px;
@@ -100,6 +100,7 @@
&:hover {
cursor: pointer;
+ background: grey;
}
}
}
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 04329182e..75fc8bf85 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,20 +1,16 @@
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 { action, observable, computed, toJS } from "mobx";
+import { Tooltip } from "@material-ui/core";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../fields/Doc";
-import { StrCast, DateCast } from "../../../fields/Types";
+import { Doc } from "../../../fields/Doc";
+import { DateCast, StrCast } from "../../../fields/Types";
import { Utils } from "../../../Utils";
import { LinkManager } from "../../util/LinkManager";
+import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
import React = require("react");
-import { DocumentView } from "../nodes/DocumentView";
-import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
-import { EditableView } from "../EditableView";
-import { RefObject } from "react";
-import { Tooltip } from "@material-ui/core";
-import { undoBatch } from "../../util/UndoManager";
library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
@@ -291,26 +287,23 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@observable openDropdown: boolean = false;
@observable showInfo: boolean = false;
@computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
- @observable private buttonColor: string = "black";
+ @observable private buttonColor: string = "";
//@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
- @undoBatch
- @action
+ @undoBatch @action
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
this.props.showLinks();
}
- @action
+ @undoBatch @action
setDescripValue = (value: string) => {
if (LinkManager.currentLink) {
LinkManager.currentLink.description = value;
this.buttonColor = "rgb(62, 133, 55)";
- setTimeout(action(() => {
- this.buttonColor = "black";
- }), 750);
+ setTimeout(action(() => this.buttonColor = ""), 750);
return true;
}
}
@@ -352,7 +345,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
></input>
</div>
<div className="linkEditor-description-add-button"
- style={{ backgroundColor: this.buttonColor }}
+ style={{ background: this.buttonColor }}
onPointerDown={this.onDown}>Set</div>
</div></div>;
}
@@ -362,7 +355,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
this.openDropdown = !this.openDropdown;
}
- @action
+ @undoBatch @action
changeFollowBehavior = (follow: string) => {
this.openDropdown = false;
Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 98e4171f0..4dc25031d 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -6,6 +6,7 @@
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 7b5fb0127..8ecde959f 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -4,14 +4,13 @@ import { DocumentView } from "../nodes/DocumentView";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
-import { Doc, Opt } from "../../../fields/Doc";
+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 { LinkDocPreview } from "../nodes/LinkDocPreview";
-import { isUndefined } from "util";
library.add(faTrash);
@@ -19,7 +18,6 @@ interface Props {
docView: DocumentView;
changeFlyout: () => void;
addDocTab: (document: Doc, where: string) => boolean;
- location: number[];
}
@observer
@@ -85,17 +83,25 @@ export class LinkMenu extends React.Component<Props> {
return linkItems;
}
+ @computed
+ get position() {
+ const docView = this.props.docView;
+ const transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse();
+ const [sptX, sptY] = transform.transformPoint(0, 0);
+ const [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight());
+ return { x: sptX, y: sptY, r: bptX, b: bptY };
+ }
+
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} >
- {!this._editingLink ? <div className="linkMenu-list" style={{
- left: this.props.location[0], top: this.props.location[1]
- }}>
- {this.renderAllGroups(groups)}
- </div> : <div className="linkMenu-listEditor" style={{
- left: this.props.location[0], top: this.props.location[1]
- }}>
+ {!this._editingLink ?
+ <div className="linkMenu-list" style={{ left: this.position.x, top: this.position.b + 15 }}>
+ {this.renderAllGroups(groups)}
+ </div> :
+ <div className="linkMenu-listEditor" style={{ left: this.position.x, top: this.position.b + 15 }}>
<LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
showLinks={action(() => this._editingLink = undefined)} />
</div>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d1c839c3b..f4aed94e7 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -167,6 +167,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
DocumentLinksButton.EditLink = undefined;
}
+ @undoBatch
@action
showLink = () => {
this.props.linkDoc.hidden = !this.props.linkDoc.hidden;
@@ -182,7 +183,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
switch (this.props.destinationDoc.type) {
case DocumentType.IMG: destinationIcon = "image"; break;
case DocumentType.COMPARISON: destinationIcon = "columns"; break;
- case DocumentType.RTF: destinationIcon = "font"; break;
+ case DocumentType.RTF: destinationIcon = "sticky-note"; break;
case DocumentType.COL: destinationIcon = "folder"; break;
case DocumentType.WEB: destinationIcon = "globe-asia"; break;
case DocumentType.SCREENSHOT: destinationIcon = "photo-video"; break;
@@ -190,12 +191,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
case DocumentType.AUDIO: destinationIcon = "microphone"; break;
case DocumentType.BUTTON: destinationIcon = "bolt"; break;
case DocumentType.PRES: destinationIcon = "tv"; break;
- case DocumentType.QUERY: destinationIcon = "search"; break;
case DocumentType.SCRIPTING: destinationIcon = "terminal"; break;
case DocumentType.IMPORT: destinationIcon = "cloud-upload-alt"; break;
case DocumentType.DOCHOLDER: destinationIcon = "expand"; break;
case DocumentType.VID: destinationIcon = "video"; break;
case DocumentType.INK: destinationIcon = "pen-nib"; break;
+ case DocumentType.PDF: destinationIcon = "file"; break;
default: destinationIcon = "question"; break;
}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 9347e9b5b..59b60defa 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -23,9 +23,8 @@ import { Networking } from "../../Network";
import { LinkAnchorBox } from "./LinkAnchorBox";
import { List } from "../../../fields/List";
import { Scripting } from "../../util/Scripting";
-import Waveform from "react-audio-waveform"
-import axios from "axios"
-import { DragManager } from "../../util/DragManager";
+import Waveform from "react-audio-waveform";
+import axios from "axios";
const _global = (window /* browser */ || global /* node */) as any;
declare class MediaRecorder {
@@ -76,13 +75,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
@observable _currX: number = 0;
@observable _position: number = 0;
@observable _buckets: Array<number> = new Array<number>();
- @observable _waveHeight = this.layoutDoc._height;
+ @observable _waveHeight: number | undefined = this.layoutDoc._height;
@observable private _paused: boolean = false;
@observable private static _scrubTime = 0;
@computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
set audioState(value) { this.dataDoc.audioState = value; }
public static SetScrubTime = (timeInMillisFrom1970: number) => { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); };
-
@computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); }
@@ -498,7 +496,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
const rect = await (e.target as any).getBoundingClientRect();
- let newTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+ const newTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
this.changeMarker(this._currMarker, newTime);
}
@@ -506,11 +504,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
// updates the marker with the new time
@action
changeMarker = (m: any, time: any) => {
- for (let i = 0; i < this.dataDoc[this.annotationKey].length; i++) {
- if (this.isSame(this.dataDoc[this.annotationKey][i], m)) {
- this._left ? this.dataDoc[this.annotationKey][i].audioStart = time : this.dataDoc[this.annotationKey][i].audioEnd = time;
+ DocListCast(this.dataDoc[this.annotationKey]).forEach((marker: Doc) => {
+ if (this.isSame(marker, m)) {
+ this._left ? marker.audioStart = time : marker.audioEnd = time;
}
- }
+ });
}
// checks if the two markers are the same with start and end time
@@ -526,7 +524,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
const increment = NumCast(this.layoutDoc.duration) / 500;
this._count = [];
for (let i = 0; i < 500; i++) {
- this._count.push([increment * i, 0])
+ this._count.push([increment * i, 0]);
}
}
@@ -537,7 +535,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._first = false;
this.markers();
}
- let max = 0
+ let max = 0;
for (let i = 0; i < 500; i++) {
if (this._count[i][0] >= m.audioStart && this._count[i][0] <= m.audioEnd) {
@@ -559,7 +557,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
if (this.dataDoc.markerAmount < max) {
this.dataDoc.markerAmount = max;
}
- return max - 1
+ return max - 1;
}
// returns the audio waveform
@@ -568,7 +566,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
color={"darkblue"}
height={this._waveHeight}
barWidth={0.1}
- // pos={this.layoutDoc.currentTimecode}
+ // pos={this.layoutDoc.currentTimecode} need to correctly resize parent to make this work (not very necessary for function)
pos={this.dataDoc.duration}
duration={this.dataDoc.duration}
peaks={this._buckets.length === 100 ? this._buckets : undefined}
@@ -578,27 +576,27 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
// decodes the audio file into peaks for generating the waveform
@action
buckets = async () => {
- let audioCtx = new (window.AudioContext)();
+ const audioCtx = new (window.AudioContext)();
axios({ url: this.path, responseType: "arraybuffer" })
.then(response => {
- let audioData = response.data;
+ const audioData = response.data;
audioCtx.decodeAudioData(audioData, action(buffer => {
- let decodedAudioData = buffer.getChannelData(0);
+ const decodedAudioData = buffer.getChannelData(0);
const NUMBER_OF_BUCKETS = 100;
- let bucketDataSize = Math.floor(decodedAudioData.length / NUMBER_OF_BUCKETS);
+ const bucketDataSize = Math.floor(decodedAudioData.length / NUMBER_OF_BUCKETS);
for (let i = 0; i < NUMBER_OF_BUCKETS; i++) {
- let startingPoint = i * bucketDataSize;
- let endingPoint = i * bucketDataSize + bucketDataSize;
+ const startingPoint = i * bucketDataSize;
+ const endingPoint = i * bucketDataSize + bucketDataSize;
let max = 0;
for (let j = startingPoint; j < endingPoint; j++) {
if (decodedAudioData[j] > max) {
max = decodedAudioData[j];
}
}
- let size = Math.abs(max);
+ const size = Math.abs(max);
this._buckets.push(size / 2);
}
@@ -636,29 +634,29 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
canvas2.style.height = `${height}`;
canvas2.style.width = `${width}`;
- let ratio1 = oldWidth / window.innerWidth;
- let ratio2 = oldHeight / window.innerHeight;
- let context = canvas2.getContext('2d');
+ const ratio1 = oldWidth / window.innerWidth;
+ const ratio2 = oldHeight / window.innerHeight;
+ const context = canvas2.getContext('2d');
if (context) {
- context.scale(ratio1, ratio2)
+ context.scale(ratio1, ratio2);
}
}
- let canvas1 = document.getElementsByTagName("canvas")[1];
+ const canvas1 = document.getElementsByTagName("canvas")[1];
if (canvas1) {
- let oldWidth = canvas1.width;
- let oldHeight = canvas1.height;
+ const oldWidth = canvas1.width;
+ const oldHeight = canvas1.height;
canvas1.style.height = `${height}`;
canvas1.style.width = `${width}`;
- let ratio1 = oldWidth / window.innerWidth;
- let ratio2 = oldHeight / window.innerHeight;
- let context = canvas1.getContext('2d');
+ const ratio1 = oldWidth / window.innerWidth;
+ const ratio2 = oldHeight / window.innerHeight;
+ const context = canvas1.getContext('2d');
if (context) {
- context.scale(ratio1, ratio2)
+ context.scale(ratio1, ratio2);
}
- let parent = canvas1.parentElement;
+ const parent = canvas1.parentElement;
if (parent) {
parent.style.width = `${width}`;
parent.style.height = `${height}`;
@@ -737,7 +735,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
(!m.isLabel) ?
(this.layoutDoc.hideMarkers) ? (null) :
rect =
- <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container1"} title={`${formatTime(Math.round(NumCast(m.audioStart)))}` + " - " + `${formatTime(Math.round(NumCast(m.audioEnd)))}`} key={i} id={"audiobox-marker-container1"} style={{ left: `${NumCast(m.audioStart) / NumCast(this.dataDoc.duration, 1) * 100}%`, width: `${(NumCast(m.audioEnd) - NumCast(m.audioStart)) / NumCast(this.dataDoc.duration, 1) * 100}%`, top: `${this.isOverlap(m) * 1 / (this.dataDoc.markerAmount + 1) * 100}%`, height: `${1 / (this.dataDoc.markerAmount + 1) * 100}%` }} onClick={e => { this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation() }} >
+ <div key={i} id={"audiobox-marker-container1"} className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container1"}
+ title={`${formatTime(Math.round(NumCast(m.audioStart)))}` + " - " + `${formatTime(Math.round(NumCast(m.audioEnd)))}`}
+ style={{
+ left: `${NumCast(m.audioStart) / NumCast(this.dataDoc.duration, 1) * 100}%`,
+ width: `${(NumCast(m.audioEnd) - NumCast(m.audioStart)) / NumCast(this.dataDoc.duration, 1) * 100}%`, height: `${1 / (this.dataDoc.markerAmount + 1) * 100}%`,
+ top: `${this.isOverlap(m) * 1 / (this.dataDoc.markerAmount + 1) * 100}%`
+ }}
+ onClick={e => { this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation(); }} >
<div className="left-resizer" onPointerDown={e => this.onPointerDown(e, m, true)}></div>
<DocumentView {...this.props}
Document={m}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ce39c3735..42a42ddf1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -11,10 +11,12 @@ import { Document } from "../../../fields/documentSchemas";
import { TraceMobx } from "../../../fields/util";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
import { List } from "../../../fields/List";
-import { numberRange } from "../../../Utils";
+import { numberRange, smoothScroll } from "../../../Utils";
import { ComputedField } from "../../../fields/ScriptField";
import { listSpec } from "../../../fields/Schema";
import { DocumentType } from "../../documents/DocumentTypes";
+import { Zoom, Fade, Flip, Rotate, Bounce, Roll, LightSpeed } from 'react-reveal';
+import { PresBox } from "./PresBox";
import { InkingStroke } from "../InkingStroke";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@@ -74,33 +76,84 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getValues(doc: Doc, time: number) {
const timecode = Math.round(time);
return ({
+ h: Cast(doc["h-indexed"], listSpec("number"), [NumCast(doc._height)]).reduce((p, h, i) => (i <= timecode && h !== undefined) || p === undefined ? h : p, undefined as any as number),
+ w: Cast(doc["w-indexed"], listSpec("number"), [NumCast(doc._width)]).reduce((p, w, i) => (i <= timecode && w !== undefined) || p === undefined ? w : p, undefined as any as number),
x: Cast(doc["x-indexed"], listSpec("number"), [NumCast(doc.x)]).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
y: Cast(doc["y-indexed"], listSpec("number"), [NumCast(doc.y)]).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
+ scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollTop, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number),
opacity: Cast(doc["opacity-indexed"], listSpec("number"), [NumCast(doc.opacity, 1)]).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
});
}
- public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
+ public static setValues(time: number, d: Doc, x?: number, y?: number, h?: number, w?: number, scroll?: number, opacity?: number) {
const timecode = Math.round(time);
+ const hindexed = Cast(d["h-indexed"], listSpec("number"), []).slice();
+ const windexed = Cast(d["w-indexed"], listSpec("number"), []).slice();
const xindexed = Cast(d["x-indexed"], listSpec("number"), []).slice();
const yindexed = Cast(d["y-indexed"], listSpec("number"), []).slice();
const oindexed = Cast(d["opacity-indexed"], listSpec("number"), []).slice();
+ const scrollIndexed = Cast(d["scroll-indexed"], listSpec("number"), []).slice();
xindexed[timecode] = x as any as number;
yindexed[timecode] = y as any as number;
+ hindexed[timecode] = h as any as number;
+ windexed[timecode] = w as any as number;
oindexed[timecode] = opacity as any as number;
+ scrollIndexed[timecode] = scroll as any as number;
d["x-indexed"] = new List<number>(xindexed);
d["y-indexed"] = new List<number>(yindexed);
+ d["h-indexed"] = new List<number>(hindexed);
+ d["w-indexed"] = new List<number>(windexed);
d["opacity-indexed"] = new List<number>(oindexed);
+ d["scroll-indexed"] = new List<number>(scrollIndexed);
+ if (d.appearFrame) {
+ if (d.appearFrame === timecode + 1) {
+ d["text-color"] = "red";
+ } else if (d.appearFrame < timecode + 1) {
+ d["text-color"] = "grey";
+ } else { d["text-color"] = "black"; }
+ } else if (d.appearFrame === 0) {
+ d["text-color"] = "black";
+ }
+ }
+
+ public static updateScrollframe(doc: Doc, time: number) {
+ const timecode = Math.round(time);
+ const scrollIndexed = Cast(doc['scroll-indexed'], listSpec("number"), null);
+ scrollIndexed?.length <= timecode + 1 && scrollIndexed.push(undefined as any as number);
+ setTimeout(() => doc.dataTransition = "inherit", 1010);
+ }
+
+ public static setupScroll(doc: Doc, timecode: number, scrollProgressivize: boolean = false) {
+ const scrollList = new List<number>();
+ scrollList[timecode] = NumCast(doc._scrollTop);
+ doc["scroll-indexed"] = scrollList;
+ doc.activeFrame = ComputedField.MakeFunction("self.currentFrame");
+ doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame");
}
+
+
public static updateKeyframe(docs: Doc[], time: number) {
const timecode = Math.round(time);
docs.forEach(doc => {
const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
+ const hindexed = Cast(doc['h-indexed'], listSpec("number"), null);
+ const windexed = Cast(doc['w-indexed'], listSpec("number"), null);
const opacityindexed = Cast(doc['opacity-indexed'], listSpec("number"), null);
+ hindexed?.length <= timecode + 1 && hindexed.push(undefined as any as number);
+ windexed?.length <= timecode + 1 && windexed.push(undefined as any as number);
xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number);
yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number);
opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number);
+ if (doc.appearFrame) {
+ if (doc.appearFrame === timecode + 1) {
+ doc["text-color"] = "red";
+ } else if (doc.appearFrame < timecode + 1) {
+ doc["text-color"] = "grey";
+ } else { doc["text-color"] = "black"; }
+ } else if (doc.appearFrame === 0) {
+ doc["text-color"] = "black";
+ }
doc.dataTransition = "all 1s";
});
setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010);
@@ -111,18 +164,49 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010);
}
+ public static setupZoom(doc: Doc, zoomProgressivize: boolean = false) {
+ const width = new List<number>();
+ const height = new List<number>();
+ const top = new List<number>();
+ const left = new List<number>();
+ width.push(NumCast(doc.width));
+ height.push(NumCast(doc.height));
+ top.push(NumCast(doc.height) / -2);
+ left.push(NumCast(doc.width) / -2);
+ doc["viewfinder-width-indexed"] = width;
+ doc["viewfinder-height-indexed"] = height;
+ doc["viewfinder-top-indexed"] = top;
+ 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 olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1));
+ 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);\
+ 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["opacity-indexed"] = olist;
+ doc["w-indexed"] = wlist;
+ doc["h-indexed"] = hlist;
+ doc["opacity-indexed"] = oarray;
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");
doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
@@ -135,6 +219,44 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this.props.Document.y = NumCast(this.props.Document.y) + y;
}
+ @computed get freeformNodeDiv() {
+ const node = <DocumentView {...this.props}
+ nudge={this.nudge}
+ dragDivName={"collectionFreeFormDocumentView-container"}
+ ContentScaling={this.contentScaling}
+ ScreenToLocalTransform={this.getTransform}
+ backgroundColor={this.props.backgroundColor}
+ opacity={this.opacity}
+ NativeHeight={this.NativeHeight}
+ NativeWidth={this.NativeWidth}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight} />;
+ if (PresBox.Instance && this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc) {
+ const effectProps = {
+ left: this.layoutDoc.presEffectDirection === 'left',
+ right: this.layoutDoc.presEffectDirection === 'right',
+ top: this.layoutDoc.presEffectDirection === 'top',
+ bottom: this.layoutDoc.presEffectDirection === 'bottom',
+ opposite: true,
+ delay: this.layoutDoc.presTransition,
+ // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc,
+ };
+ switch (this.layoutDoc.presEffect) {
+ case "Zoom": return (<Zoom {...effectProps}>{node}</Zoom>); break;
+ case "Fade": return (<Fade {...effectProps}>{node}</Fade>); break;
+ case "Flip": return (<Flip {...effectProps}>{node}</Flip>); break;
+ case "Rotate": return (<Rotate {...effectProps}>{node}</Rotate>); break;
+ case "Bounce": return (<Bounce {...effectProps}>{node}</Bounce>); break;
+ case "Roll": return (<Roll {...effectProps}>{node}</Roll>); break;
+ case "LightSpeed": return (<LightSpeed {...effectProps}>{node}</LightSpeed>); break;
+ case "None": return node; break;
+ default: return node; break;
+ }
+ } else {
+ return node;
+ }
+ }
+
contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
@@ -165,6 +287,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
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
}} >
+
{Doc.UserDoc().renderStyle !== "comic" ? (null) :
<div style={{ width: "100%", height: "100%", position: "absolute" }}>
<svg style={{ transform: `scale(1,${this.props.PanelHeight() / this.props.PanelWidth()})`, transformOrigin: "top left", overflow: "visible" }} viewBox="0 0 12 14">
@@ -174,17 +297,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
</div>}
{!this.props.fitToBox ?
- <DocumentView {...this.props}
- nudge={this.nudge}
- dragDivName={"collectionFreeFormDocumentView-container"}
- ContentScaling={this.contentScaling}
- ScreenToLocalTransform={this.getTransform}
- backgroundColor={this.props.backgroundColor}
- opacity={this.opacity}
- NativeHeight={this.NativeHeight}
- NativeWidth={this.NativeWidth}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight} />
+ <>{this.freeformNodeDiv}</>
: <ContentFittingDocumentView {...this.props}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
DataDoc={this.props.DataDoc}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 57028b0ca..090cf015a 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -61,6 +61,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
<SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
color={StrCast(ActiveInkPen()?.backgroundColor,
StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} />
+
<div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
<div> {ActiveInkWidth() ?? 2}</div>
<input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f140cc6e5..616cddfcf 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -24,7 +24,7 @@ const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
- protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+ protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
@observable _animating = "";
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 47dc0a773..2408b3906 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -18,13 +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 { ImageBox } from "./ImageBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { PresBox } from "./PresBox";
-import { QueryBox } from "./QueryBox";
+import { SearchBox } from "../search/SearchBox";
import { ColorBox } from "./ColorBox";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
import { LinkAnchorBox } from "./LinkAnchorBox";
@@ -35,7 +36,6 @@ import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
import React = require("react");
-import { RecommendationsBox } from "../RecommendationsBox";
import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
import { ScriptField } from "../../../fields/ScriptField";
import XRegExp = require("xregexp");
@@ -190,11 +190,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
blacklistedAttrs={[]}
renderInWrapper={false}
components={{
- FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
+ FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, MenuIconBox, LabelBox, SliderBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
+ PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox,
ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
- RecommendationsBox, ScreenshotBox, HTMLtag, ComparisonBox
+ ScreenshotBox, HTMLtag, ComparisonBox
}}
bindings={bindings}
jsx={layoutFrame}
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 97e714cd5..9328fb96b 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -28,7 +28,13 @@
}
.documentLinksButton {
- background-color: $link-color;
+ background-color: black;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
.documentLinksButton-endLink {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c9d23ff3a..c2f27c85a 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,19 +1,19 @@
+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 { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { Doc } from "../../../fields/Doc";
+import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, returnFalse, setupMoveUpEvents, emptyPath } from "../../../Utils";
+import { DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
-import { UndoManager, undoBatch } from "../../util/UndoManager";
+import { LinkManager } from "../../util/LinkManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import './DocumentLinksButton.scss';
import { DocumentView } from "./DocumentView";
-import React = require("react");
-import { DocUtils } from "../../documents/Documents";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { LinkDocPreview } from "./LinkDocPreview";
-import { TaskCompletionBox } from "./TaskCompletedBox";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { LinkManager } from "../../util/LinkManager";
-import { Tooltip } from "@material-ui/core";
+import { TaskCompletionBox } from "./TaskCompletedBox";
+import React = require("react");
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -24,6 +24,7 @@ interface DocumentLinksButtonProps {
AlwaysOn?: boolean;
InMenu?: boolean;
StartLink?: boolean;
+ links: Doc[];
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -66,10 +67,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
if (doubleTap && this.props.InMenu && this.props.StartLink) {
//action(() => Doc.BrushDoc(this.props.View.Document));
- DocumentLinksButton.StartLink = this.props.View;
+ if (DocumentLinksButton.StartLink === this.props.View) {
+ DocumentLinksButton.StartLink = undefined;
+ } else {
+ DocumentLinksButton.StartLink = this.props.View;
+ }
} else if (!this.props.InMenu) {
DocumentLinksButton.EditLink = this.props.View;
- DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}));
}
@@ -77,88 +81,79 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@action @undoBatch
onLinkClick = (e: React.MouseEvent): void => {
if (this.props.InMenu && this.props.StartLink) {
- DocumentLinksButton.StartLink = this.props.View;
+ if (DocumentLinksButton.StartLink === this.props.View) {
+ DocumentLinksButton.StartLink = undefined;
+ } else {
+ DocumentLinksButton.StartLink = this.props.View;
+ }
+
//action(() => Doc.BrushDoc(this.props.View.Document));
} else if (!this.props.InMenu) {
DocumentLinksButton.EditLink = this.props.View;
- DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}
- @action @undoBatch
completeLink = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
- if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => {
+ if (doubleTap && this.props.InMenu && !this.props.StartLink) {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
- } else {
-
- if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
- const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
- LinkManager.currentLink = linkDoc;
-
- runInAction(() => {
- if (linkDoc) {
- TaskCompletionBox.textDisplayed = "Link Created";
- TaskCompletionBox.popupX = e.screenX;
- TaskCompletionBox.popupY = e.screenY - 133;
- TaskCompletionBox.taskCompleted = true;
-
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
+ } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+ const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ LinkManager.currentLink = linkDoc;
+ if (linkDoc) {
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = e.screenX;
+ TaskCompletionBox.popupY = e.screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
- setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
- }
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
- });
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
}
}
}
- }));
+ })));
}
-
- @action @undoBatch
- finishLinkClick = (e: React.MouseEvent) => {
+ finishLinkClick = undoBatch(action((screenX: number, screenY: number) => {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
- } else {
- if (this.props.InMenu && !!!this.props.StartLink) {
- if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
- const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "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
- runInAction(() => DocumentLinksButton.StartLink!._link = this.props.View._link = linkDoc);
- setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
- LinkManager.currentLink = linkDoc;
-
- runInAction(() => {
- if (linkDoc) {
- TaskCompletionBox.textDisplayed = "Link Created";
- TaskCompletionBox.popupX = e.screenX;
- TaskCompletionBox.popupY = e.screenY - 133;
- TaskCompletionBox.taskCompleted = true;
-
- if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
- }
-
- setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
- }
- });
+ } else if (this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+ const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "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
+ DocumentLinksButton.StartLink._link = this.props.View._link = linkDoc;
+ setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
+ LinkManager.currentLink = linkDoc;
+ if (linkDoc) {
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = screenX;
+ TaskCompletionBox.popupY = screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
+
+ if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
+ LinkDescriptionPopup.popupX = screenX;
+ LinkDescriptionPopup.popupY = screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
}
+
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
}
}
- }
+ }));
@observable
public static EditLink: DocumentView | undefined;
- public static EditLinkLoc: number[] = [0, 0];
+
+ @action clearLinks() {
+ DocumentLinksButton.StartLink = undefined;
+ }
@computed
get linkButton() {
- const links = DocListCast(this.props.View.props.Document.links);
+ TraceMobx();
+ const links = this.props.links;
const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
const buttonTitle = "Tap to view links";
@@ -182,7 +177,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
<div className={"documentLinksButton"} style={{
- backgroundColor: this.props.InMenu ? "black" : "",
+ backgroundColor: this.props.InMenu ? "" : "#add8e6",
color: this.props.InMenu ? "white" : "black",
width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
}}
@@ -196,22 +191,33 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// }))}
>
+ {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
+ <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" /> :
- <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length}
+ link : links.length}
</div>
- {DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
- style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
- onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)}
+ {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ?
+ <div className={"documentLinksButton-endLink"}
+ style={{
+ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px",
+ backgroundColor: DocumentLinksButton.StartLink ? "" : "grey",
+ border: DocumentLinksButton.StartLink ? "" : "none"
+ }}
+ onPointerDown={DocumentLinksButton.StartLink ? this.completeLink : emptyFunction}
+ onClick={e => DocumentLinksButton.StartLink ? this.finishLinkClick(e.screenX, e.screenY) : emptyFunction} /> : (null)}
{DocumentLinksButton.StartLink === this.props.View && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
- style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
+ style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
+ onPointerDown={this.clearLinks} onClick={this.clearLinks}
+ /> : (null)}
</div>;
return (!links.length) && !this.props.AlwaysOn ? (null) :
- this.props.InMenu ?
+ this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ?
<Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
{linkButton}
- </Tooltip> : !!!DocumentLinksButton.EditLink ?
+ </Tooltip> : !!!DocumentLinksButton.EditLink && !this.props.InMenu ?
<Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
{linkButton}
</Tooltip> :
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index b978f6245..e6b8928d4 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -54,6 +54,15 @@
}
}
+ .documentView-anchorCont {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: inline-block;
+ }
+
.documentView-lock {
width: 20;
height: 20;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 77932d58e..444583af3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,31 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit, AclAdmin } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
+import { MobileInterface } from '../../../mobile/MobileInterface';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
+import { emptyFunction, emptyPath, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { ClientRecommender } from '../../ClientRecommender';
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 { InteractionUtils } from '../../util/InteractionUtils';
+import { LinkManager } from '../../util/LinkManager';
import { Scripting } from '../../util/Scripting';
import { SearchUtil } from '../../util/SearchUtil';
import { SelectionManager } from "../../util/SelectionManager";
import SharingManager from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -35,19 +35,13 @@ import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
import { KeyphraseQueryView } from '../KeyphraseQueryView';
import { DocumentContentsView } from "./DocumentContentsView";
+import { DocumentLinksButton } from './DocumentLinksButton';
import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { RadialMenu } from './RadialMenu';
-import React = require("react");
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { MobileInterface } from '../../../mobile/MobileInterface';
import { TaskCompletionBox } from './TaskCompletedBox';
-import { LinkDescriptionPopup } from './LinkDescriptionPopup';
-import { LinkManager } from '../../util/LinkManager';
-
-library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
- fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, 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);
+import React = require("react");
export type DocFocusFunc = () => boolean;
@@ -100,29 +94,31 @@ export interface DocumentViewProps {
layoutKey?: string;
radialMenu?: String[];
display?: string;
+ relative?: boolean;
scriptContext?: any;
}
@observer
export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
+ @observable _animateScalingTo = 0;
private _downX: number = 0;
private _downY: number = 0;
+ private _firstX: number = -1;
+ private _firstY: number = -1;
private _lastTap: number = 0;
private _doubleTap = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
private _showKPQuery: boolean = false;
private _queries: string = "";
- private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
private _titleRef = React.createRef<EditableView>();
+ private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
+ private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
- public get title() { return this.props.Document.title; }
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
- get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+ 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)); }
@@ -136,9 +132,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClickFunc = () => this.onClickHandler;
onDoubleClickFunc = () => this.onDoubleClickHandler;
- private _firstX: number = -1;
- private _firstY: number = -1;
-
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -152,11 +145,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._firstX = pt.pageX;
this._firstY = pt.pageY;
}
-
}
handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
if (this._firstX === -1 || this._firstY === -1) {
@@ -188,7 +179,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ (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: "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 });
@@ -200,7 +192,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentDidMount() {
this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document));
this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
- this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
+ 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) {
@@ -212,13 +204,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentDidUpdate() {
this._dropDisposer?.();
this._gestureEventDisposer?.();
- this.multiTouchDisposer?.();
- this.holdDisposer?.();
+ this._multiTouchDisposer?.();
+ this._holdDisposer?.();
if (this._mainCont.current) {
this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
- this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
- this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+ this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+ this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
}
}
@@ -226,8 +218,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentWillUnmount() {
this._dropDisposer?.();
this._gestureEventDisposer?.();
- this.multiTouchDisposer?.();
- this.holdDisposer?.();
+ this._multiTouchDisposer?.();
+ this._holdDisposer?.();
Doc.UnBrushDoc(this.props.Document);
if (!this.props.dontRegisterView) {
const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -242,27 +234,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
dragData.removeDocument = this.props.removeDocument;
- dragData.moveDocument = this.props.moveDocument;// this.layoutDoc.onDragStart ? undefined : this.props.moveDocument;
+ dragData.moveDocument = this.props.moveDocument;
dragData.dragDivName = this.props.dragDivName;
dragData.treeViewDoc = this.props.treeViewDoc;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart });
}
}
- public static FloatDoc(topDocView: DocumentView, x: number, y: number) {
+ @undoBatch @action
+ public static FloatDoc(topDocView: DocumentView, x?: number, y?: number) {
const topDoc = topDocView.props.Document;
- const de = new DragManager.DocumentDragData([topDoc]);
- de.dragDivName = topDocView.props.dragDivName;
- de.moveDocument = topDocView.props.moveDocument;
- undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
- setTimeout(() => {
- const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
- if (newDocView) {
- const contentDiv = newDocView.ContentDiv!;
- const xf = contentDiv.getBoundingClientRect();
- DragManager.StartDocumentDrag([contentDiv], de, x, y, { offsetX: x - xf.left, offsetY: y - xf.top, hideSource: true });
+ const container = topDocView.props.ContainingCollectionView;
+ if (container) {
+ SelectionManager.DeselectAll();
+ if (topDoc.z && (x === undefined && y === undefined)) {
+ const spt = container.screenToLocalTransform().inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
+ topDoc.z = 0;
+ topDoc.x = spt[0];
+ topDoc.y = spt[1];
+ topDocView.props.removeDocument?.(topDoc);
+ topDocView.props.addDocTab(topDoc, "inParent");
+ } else {
+ const spt = topDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const fpt = container.screenToLocalTransform().transformPoint(x !== undefined ? x : spt[0], y !== undefined ? y : spt[1]);
+ topDoc.z = 1;
+ topDoc.x = fpt[0];
+ topDoc.y = fpt[1];
}
- }, 0);
+ setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0);
+ }
}
onKeyDown = (e: React.KeyboardEvent) => {
@@ -290,7 +290,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
+ 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;
@@ -301,42 +301,40 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const func = () => this.onDoubleClickHandler.script.run({
this: this.layoutDoc,
self: this.rootDoc,
- thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ thisContainer: this.props.ContainingCollectionDoc,
+ 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) {
- const fullScreenAlias = Doc.MakeAlias(this.props.Document);
- fullScreenAlias.layoutKey = "layout_fullScreen";
- this.props.addDocTab(fullScreenAlias, "inTab");
- } else {
- this.props.addDocTab(this.props.Document, "inTab");
+ fullScreenDoc = Doc.MakeAlias(this.props.Document);
+ fullScreenDoc.layoutKey = "layout_fullScreen";
}
+ this.props.addDocTab(fullScreenDoc, "inTab");
}, "double tap");
SelectionManager.DeselectAll();
Doc.UnBrushDoc(this.props.Document);
}
}
} else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
- //SelectionManager.DeselectAll();
const func = () => this.onClickHandler.script.run({
this: this.layoutDoc,
self: this.rootDoc,
scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ thisContainer: this.props.ContainingCollectionDoc,
+ shiftKey: e.shiftKey
}, console.log);
- if (this.props.Document !== Doc.UserDoc()["dockedBtn-undo"] && this.props.Document !== Doc.UserDoc()["dockedBtn-redo"]) {
+ 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();
} 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
- const alias = Doc.MakeAlias(this.props.Document);
- DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
- this.props.addDocTab(alias, "onRight");
- } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
- DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
+ this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
+ } 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
+ 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);
@@ -408,7 +406,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
-
}
}
@@ -446,12 +443,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const oldPoint2 = this.prevPoints.get(pt2.identifier);
const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
if (pinching !== 0 && oldPoint1 && oldPoint2) {
- // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
- // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
- // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
- // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
- // let dW = -dX;
- // let dH = -dY;
const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
const dX = -1 * Math.sign(dW);
@@ -534,7 +525,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerMove = (e: PointerEvent): void => {
-
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return;
if (e.cancelBubble && this.active) {
@@ -555,17 +545,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerUp = (e: PointerEvent): void => {
this.cleanUpInteractions();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
- document.removeEventListener("pointerup", this.onPointerUp);
- return;
+ } else {
+ this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+ this._lastTap = Date.now();
}
-
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
- this._lastTap = Date.now();
}
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
@@ -583,46 +571,37 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
alert("Can't delete the active workspace");
} else {
SelectionManager.DeselectAll();
+ this.props.Document.deleted = true;
this.props.removeDocument?.(this.props.Document);
}
}
-
- @undoBatch
- toggleLinkButtonBehavior = (): void => {
+ @undoBatch @action
+ toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
this.Document.ignoreClick = false;
- if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) {
- this.Document.isLinkButton = false;
- this.Document.onClick = this.layoutDoc.onClick = undefined;
+ this.Document.isLinkButton = !this.Document.isLinkButton;
+ setPushpin && (this.Document.isPushpin = this.Document.isLinkButton);
+ if (this.Document.isLinkButton && !this.onClickHandler) {
+ this.Document.followLinkZoom = zoom;
+ this.Document.followLinkLocation = location;
} else {
- this.Document.isLinkButton = true;
- this.Document.followLinkZoom = false;
- this.Document.followLinkLocation = undefined;
+ this.Document.onClick = this.layoutDoc.onClick = undefined;
}
}
+
@undoBatch
- toggleFollowInPlace = (): void => {
+ noOnClick = (): void => {
this.Document.ignoreClick = false;
- this.Document.isLinkButton = !this.Document.isLinkButton;
- if (this.Document.isLinkButton) {
- this.Document.followLinkZoom = true;
- this.Document.followLinkLocation = "inPlace";
- }
+ this.Document.isLinkButton = false;
}
@undoBatch
- toggleFollowOnRight = (): void => {
- this.Document.ignoreClick = false;
- this.Document.isLinkButton = !this.Document.isLinkButton;
- if (this.Document.isLinkButton) {
- this.Document.followLinkZoom = false;
- this.Document.followLinkLocation = "onRight";
- }
+ toggleDetail = (): void => {
+ this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`);
}
- @undoBatch
- @action
+ @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.");
@@ -652,11 +631,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
if (de.complete.linkDragData) {
e.stopPropagation();
- if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
- const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
- { doc: this.props.Document }, `link`);
- de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
- (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+ const linkSource = de.complete.linkDragData.linkSourceDocument;
+ if (linkSource !== this.props.Document) {
+ const linkDoc = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, `link`);
+ linkSource !== this.props.Document && (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
linkDoc && makeLink(linkDoc);
}
@@ -671,8 +649,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
+ toggleLockPosition = (): void => {
+ this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
+ }
+
+ @undoBatch
+ @action
makeIntoPortal = async () => {
- const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document);
+ const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
if (!portalLink) {
const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
@@ -683,9 +667,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- toggleBackground = (temporary: boolean): void => {
- this.Document._overflow = temporary ? "visible" : "hidden";
- this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
+ toggleBackground = () => {
+ 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]();
@@ -693,39 +677,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- @undoBatch
- @action
- toggleLockPosition = (): void => {
- this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
- }
-
- @undoBatch
@action
- setAcl = (acl: SharingPermissions) => {
- this.dataDoc.ACL = this.props.Document.ACL = acl;
- DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
- if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
- const data = d[DataSym];
- if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
- });
- }
-
- @undoBatch
- @action
- testAcl = (acl: SharingPermissions) => {
- this.dataDoc.author = this.props.Document.author = "ADMIN";
- this.dataDoc.ACL = this.props.Document.ACL = acl;
- DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
- if (d.author === Doc.CurrentUserEmail) {
- d.author = "ADMIN";
- d.ACL = acl;
- }
- const data = d[DataSym];
- if (data && data.author === Doc.CurrentUserEmail) {
- data.author = "ADMIN";
- data.ACL = acl;
- }
- });
+ onCopy = () => {
+ const alias = Doc.MakeAlias(this.props.Document);
+ alias.x = NumCast(this.props.Document.x) + NumCast(this.props.Document._width);
+ alias.y = NumCast(this.props.Document.y) + 30;
+ this.props.addDocument?.(alias);
}
@action
@@ -772,13 +729,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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: "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.toggleFollowInPlace, icon: "concierge-bell" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
- onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
- !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" });
+ 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" });
const funcs: ContextMenuProps[] = [];
if (this.layoutDoc.onDragStart) {
@@ -790,16 +748,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const more = cm.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
- moreItems.push({
- description: "Download document", icon: "download", event: async () => {
- Doc.Zip(this.props.Document);
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- }
- });
+ moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) });
+ moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "users" });
+ //moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ //moreItems.push({ description: "Create an Alias", event: () => this.onCopy(), icon: "copy" });
if (!Doc.UserDoc().noviceMode) {
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -810,193 +762,29 @@ 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" });
}
+
const effectiveAcl = GetEffectiveAcl(this.props.Document);
(effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
- moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
+
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
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" });
- 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: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
- // const existingAcls = cm.findByDescription("Privacy...");
- // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
- // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
- // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
- // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
- // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
-
- // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
-
- // const recommender_subitems: ContextMenuProps[] = [];
-
- // recommender_subitems.push({
- // description: "Internal recommendations",
- // event: () => this.recommender(),
- // icon: "brain"
- // });
-
- // const ext_recommender_subitems: ContextMenuProps[] = [];
-
- // ext_recommender_subitems.push({
- // description: "arXiv",
- // event: () => this.externalRecommendation("arxiv"),
- // icon: "brain"
- // });
- // ext_recommender_subitems.push({
- // description: "Bing",
- // event: () => this.externalRecommendation("bing"),
- // icon: "brain"
- // });
-
- // recommender_subitems.push({
- // description: "External recommendations",
- // subitems: ext_recommender_subitems,
- // icon: "brain"
- // });
-
-
- //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
- //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
- //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
-
- // runInAction(() => {
- // const setWriteMode = (mode: DocServer.WriteMode) => {
- // DocServer.AclsMode = mode;
- // const mode1 = mode;
- // const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- // DocServer.setFieldWriteMode("x", mode1);
- // DocServer.setFieldWriteMode("y", mode1);
- // DocServer.setFieldWriteMode("_width", mode1);
- // DocServer.setFieldWriteMode("_height", mode1);
-
- // DocServer.setFieldWriteMode("_panX", mode2);
- // DocServer.setFieldWriteMode("_panY", mode2);
- // DocServer.setFieldWriteMode("scale", mode2);
- // DocServer.setFieldWriteMode("_viewType", mode2);
- // };
- // const aclsMenu: ContextMenuProps[] = [];
- // aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
- // aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
- // aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
- // aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
- // cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
- // });
runInAction(() => {
if (!this.topMost && !(e instanceof Touch)) {
- // DocumentViews should stop propagation of this event
- e.stopPropagation();
+ e.stopPropagation(); // DocumentViews should stop propagation of this event
}
cm.displayMenu(e.pageX - 15, e.pageY - 15);
!SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
});
- const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
- const item = ({
- description: `path: ${path}`, event: () => {
- if (this.props.LibraryPath !== emptyPath) {
- this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
- Doc.linkFollowHighlight(this.props.Document);
- } else {
- Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
- }
- }, icon: "check"
- });
- //cm.addItem(item);
- }
-
- recommender = async () => {
- if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
- const documents: Doc[] = [];
- const allDocs = await SearchUtil.GetAllDocs();
- // clears internal representation of documents as vectors
- ClientRecommender.Instance.reset_docs();
- //ClientRecommender.Instance.arxivrequest("electrons");
- await Promise.all(allDocs.map((doc: Doc) => {
- let isMainDoc: boolean = false;
- const dataDoc = Doc.GetProto(doc);
- if (doc.type === DocumentType.RTF) {
- if (dataDoc === Doc.GetProto(this.props.Document)) {
- isMainDoc = true;
- }
- if (!documents.includes(dataDoc)) {
- documents.push(dataDoc);
- const extdoc = doc.data_ext as Doc;
- return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc);
- }
- }
- if (doc.type === DocumentType.IMG) {
- if (dataDoc === Doc.GetProto(this.props.Document)) {
- isMainDoc = true;
- }
- if (!documents.includes(dataDoc)) {
- documents.push(dataDoc);
- const extdoc = doc.data_ext as Doc;
- return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true);
- }
- }
- }));
- const doclist = ClientRecommender.Instance.computeSimilarities("cosine");
- const recDocs: { preview: Doc, score: number }[] = [];
- // tslint:disable-next-line: prefer-for-of
- for (let i = 0; i < doclist.length; i++) {
- recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
- }
-
- const data = recDocs.map(unit => {
- unit.preview.score = unit.score;
- return unit.preview;
- });
-
- console.log(recDocs.map(doc => doc.score));
-
- const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`;
- const recommendations = Docs.Create.RecommendationsDocument(data, { title });
- recommendations.documentIconHeight = 150;
- recommendations.sourceDoc = this.props.Document;
- recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
- this.props.addDocTab(recommendations, "onRight");
-
- // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
- }
-
- @action
- externalRecommendation = async (api: string) => {
- if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
- ClientRecommender.Instance.reset_docs();
- const doc = Doc.GetDataDoc(this.props.Document);
- const extdoc = doc.data_ext as Doc;
- const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api);
- let recs: any;
- let kps: any;
- if (recs_and_kps) {
- recs = recs_and_kps.recs;
- kps = recs_and_kps.keyterms;
- }
- else {
- console.log("recommender system failed :(");
- return;
- }
- console.log("ibm keyterms: ", kps.toString());
- const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")];
- const bodies: Doc[] = [];
- const titles = recs.title_vals;
- const urls = recs.url_vals;
- for (let i = 0; i < 5; i++) {
- const body = Docs.Create.FreeformDocument([], { title: titles[i] });
- body.href = urls[i];
- bodies.push(body);
- }
- this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight");
- this._showKPQuery = true;
- this._queries = kps.toString();
}
// does Document set a layout prop
@@ -1026,9 +814,11 @@ 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 get contents() {
+ const pos = this.props.relative ? "relative " : "absolute";
TraceMobx();
- return (<div style={{ position: "absolute", width: "100%", height: "100%" }}>
+ return (<div style={{ width: "100%", height: "100%" }}>
<DocumentContentsView key={1}
docFilters={this.props.docFilters}
ContainingCollectionView={this.props.ContainingCollectionView}
@@ -1064,18 +854,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
select={this.select}
+ scriptContext={this.props.scriptContext}
onClick={this.onClickFunc}
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
{/* {this.allAnchors} */}
{this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) :
- <DocumentLinksButton View={this} Offset={[-15, 0]} />}
+ <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
</div>
);
}
// used to decide whether a link anchor view should be created or not.
- // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
+ // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
@@ -1083,7 +874,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
}
-
@observable _link: Opt<Doc>; // see DocumentButtonBar for explanation of how this works
makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
@@ -1092,32 +882,40 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
- @computed get allAnchors() {
+ @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.Document); }
+ @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
this.layoutDoc.presBox || // presentationbox nodes
+ this.rootDoc.type === DocumentType.LINK ||
this.props.dontRegisterView ? (null) : // view that are not registered
- DocUtils.FilterDocs(LinkManager.Instance.getAllDirectLinks(this.Document), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink(d)).map((d, i) =>
- <DocumentView {...this.props} key={i + 1}
- 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)}`)}
- />);
+ 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 >);
}
@computed get innards() {
TraceMobx();
+ const pos = this.props.relative ? "relative" : undefined;
if (this.props.treeViewDoc && !this.props.LayoutTemplateString?.includes("LinkAnchorBox")) { // this happens when the document is a tree view label (but not an anchor dot)
- return <div className="documentView-treeView" style={{ maxWidth: this.props.PanelWidth() || undefined }}>
+ return <div className="documentView-treeView" style={{
+ maxWidth: this.props.PanelWidth() || undefined,
+ position: pos
+ }}>
{StrCast(this.props.Document.title)}
{this.allAnchors}
</div>;
@@ -1171,7 +969,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
}
}
- @observable _animateScalingTo = 0;
+
switchViews = action((custom: boolean, view: string) => {
this._animateScalingTo = 0.1; // shrink doc
setTimeout(action(() => {
@@ -1185,7 +983,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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(true)}>
+ <div className="documentView-lock" onClick={this.toggleBackground}>
<FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
</div>
: (null);
@@ -1202,14 +1000,14 @@ 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 ?
["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK;
- highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
- return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+ highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
+ const topmost = this.topMost ? "-topmost" : "";
+ return <div className={`documentView-node${topmost}`}
id={this.props.Document[Id]}
ref={this._mainCont} onKeyDown={this.onKeyDown}
onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
@@ -1242,7 +1040,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
background: finalColor,
opacity: finalOpacity,
fontFamily: StrCast(this.Document._fontFamily, "inherit"),
- fontSize: Cast(this.Document._fontSize, "string", null)
+ fontSize: Cast(this.Document._fontSize, "string", null),
}}>
{this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
{this.innards}
@@ -1251,7 +1049,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.innards}
{this.renderLock()}
</div>;
- { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
}
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 48e1f6ce3..e631ad5fe 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -45,14 +45,22 @@ export interface FieldViewProps {
whenActiveChanged: (isActive: boolean) => void;
dontRegisterView?: boolean;
focus: (doc: Doc) => void;
+ presMultiSelect?: (doc: Doc) => void; //added for selecting multiple documents in a presentation
ignoreAutoHeight?: boolean;
PanelWidth: () => number;
PanelHeight: () => number;
+ PanelPosition?: string;
+ overflow?: boolean;
NativeHeight: () => number;
NativeWidth: () => number;
setVideoBox?: (player: VideoBox) => void;
ContentScaling: () => number;
+
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;
@@ -60,6 +68,7 @@ export interface FieldViewProps {
color?: string;
xMargin?: number;
yMargin?: number;
+ scriptContext?: any;
}
@observer
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 5b85d8b0b..9709e1dbd 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -1,25 +1,67 @@
-.fontIconBox-outerDiv {
+.fontIconBox-label {
+ color: white;
+ margin-right: 4px;
+ margin-top: 1px;
+ position: relative;
+ text-align: center;
+ font-size: 7px;
+ letter-spacing: normal;
+ background-color: inherit;
+ border-radius: 8px;
+ margin-top: -8px;
+ padding: 0;
+ width: 100%;
+}
+
+.menuButton-round {
+ border-radius: 100%;
+ background-color: black;
+
+ .fontIconBox-label {
+ margin-left: -10px; // button padding is 10px;
+ bottom: 0;
+ position: absolute;
+ }
+
+ &:hover {
+ background-color: #aaaaa3;
+ }
+}
+
+.menuButton-square {
+ padding-top: 3px;
+ padding-bottom: 3px;
+ padding-left: 5px;
+
+ .fontIconBox-label {
+ border-radius: 0px;
+ margin-top: 0px;
+ border-radius: "inherit";
+ }
+}
+
+.menuButton,
+.menuButton-round,
+.menuButton-square {
width: 100%;
height: 100%;
pointer-events: all;
touch-action: none;
- border-radius: inherit;
- background: black;
- border-radius: 100%;
- transform-origin: top left;
- .fontIconBox-label {
- background: gray;
- color:white;
+ .menuButton-wrap {
+ touch-action: none;
border-radius: 8px;
- width:100%;
- position: absolute;
- text-align: center;
- font-size: 8px;
- margin-top:4px;
- letter-spacing: normal;
- left: 0;
- overflow: hidden;
+
+ // &:hover {
+ // background: rgb(61, 61, 61);
+ // cursor: pointer;
+ // }
+ }
+
+ .menuButton-icon-square {
+ width: auto;
+ height: 32px;
+ padding: 4px;
}
svg {
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 2611d2ca7..c0eb78d98 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -6,14 +6,14 @@ import { DocComponent } from '../DocComponent';
import './FontIconBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import { StrCast, Cast, NumCast } from '../../../fields/Types';
-import { Utils } from "../../../Utils";
+import { Utils, emptyFunction } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
import { Doc } from '../../../fields/Doc';
import { ContextMenu } from '../ContextMenu';
import { ScriptField } from '../../../fields/ScriptField';
import { Tooltip } from '@material-ui/core';
const FontIconSchema = createSchema({
- icon: "string"
+ icon: "string",
});
type FontIconDocument = makeInterface<[typeof FontIconSchema]>;
@@ -59,16 +59,17 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
}
render() {
- const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc);
- const refLayout = Doc.Layout(referenceDoc);
- const button = <button className="fontIconBox-outerDiv" ref={this._ref} onContextMenu={this.specificContextMenu}
- style={{
- padding: Cast(this.layoutDoc._xPadding, "number", null),
- background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
- boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
- }}>
- <FontAwesomeIcon className="fontIconBox-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
- {!this.rootDoc.title ? (null) : <div className="fontIconBox-label" style={{ width: this.rootDoc.label ? "max-content" : undefined }}> {StrCast(this.rootDoc.label, StrCast(this.rootDoc.title).substring(0, 6))} </div>}
+ 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)));
+ const shape = StrCast(this.layoutDoc.iconShape, "round");
+ 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}
+ size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />}
+ {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>}
+ </div>
</button>;
return !this.layoutDoc.toolTip ? button :
<Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5f689624c..d668d332b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -66,7 +66,7 @@ const uploadIcons = {
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
- protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+ protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _dropDisposer?: DragManager.DragDropDisposer;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 0dfbdc5cf..826ccd340 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,4 +1,4 @@
-import { action } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
}, icon: "trash"
});
- ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "asterisk" });
+ ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" });
}
@undoBatch
@@ -56,16 +56,26 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
e.stopPropagation();
}
}
+
+ @observable _mouseOver = false;
+ @computed get backColor() { return this.clicked || this._mouseOver ? StrCast(this.layoutDoc.hovercolor) : "unset"; }
+
+ @observable clicked = false;
// (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
return (
- <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
+ <div className="labelBox-outerDiv"
+ onClick={action(() => this.clicked = !this.clicked)}
+ onMouseLeave={action(() => this._mouseOver = false)}
+ onMouseOver={action(() => this._mouseOver = true)}
+ ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
style={{ boxShadow: this.layoutDoc.opacity ? StrCast(this.layoutDoc.boxShadow) : "" }}>
<div className="labelBox-mainButton" style={{
background: StrCast(this.layoutDoc.backgroundColor),
+ backgroundColor: this.backColor,
color: StrCast(this.layoutDoc.color, "inherit"),
fontSize: StrCast(this.layoutDoc._fontSize) || "inherit",
fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 2fc002b9e..50b2af0d7 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -49,14 +49,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
const bounds = cdiv.getBoundingClientRect();
const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
- const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
if (separation > 100) {
const dragData = new DragManager.DocumentDragData([this.rootDoc]);
dragData.dropAction = "alias";
dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isLinkButton"];
- DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
+ DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
return true;
- } else if (dragdist > separation) {
+ } else {
this.rootDoc[this.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
this.rootDoc[this.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index d8fe47f4e..720af6c9d 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -19,12 +19,15 @@ export class LinkDescriptionPopup extends React.Component<{}> {
@action
descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- LinkManager.currentLink && (LinkManager.currentLink.description = e.currentTarget.value);
+ this.description = e.currentTarget.value;
}
@action
- onDismiss = () => {
+ onDismiss = (add: boolean) => {
LinkDescriptionPopup.descriptionPopup = false;
+ if (add) {
+ LinkManager.currentLink && (LinkManager.currentLink.description = this.description);
+ }
}
@action
@@ -50,15 +53,16 @@ export class LinkDescriptionPopup extends React.Component<{}> {
left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
}}>
- <input className="linkDescriptionPopup-input" onKeyPress={e => e.key === "Enter" && this.onDismiss()}
+ <input className="linkDescriptionPopup-input"
+ onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
placeholder={"(optional) enter link label..."}
onChange={(e) => this.descriptionChanged(e)}>
</input>
<div className="linkDescriptionPopup-btn">
<div className="linkDescriptionPopup-btn-dismiss"
- onPointerDown={this.onDismiss}> Dismiss </div>
+ onPointerDown={e => this.onDismiss(false)}> Dismiss </div>
<div className="linkDescriptionPopup-btn-add"
- onPointerDown={this.onDismiss}> Add </div>
+ onPointerDown={e => this.onDismiss(true)}> Add </div>
</div>
</div>;
}
diff --git a/src/client/views/nodes/MenuIconBox.scss b/src/client/views/nodes/MenuIconBox.scss
new file mode 100644
index 000000000..1b72f5a8f
--- /dev/null
+++ b/src/client/views/nodes/MenuIconBox.scss
@@ -0,0 +1,49 @@
+.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
new file mode 100644
index 000000000..0aa7b327e
--- /dev/null
+++ b/src/client/views/nodes/MenuIconBox.tsx
@@ -0,0 +1,33 @@
+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) === "lightgrey" ? "black" : "white";
+ const menuBTN = <div className="menuButton" style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document) }}>
+ <div className="menuButton-wrap"
+ style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document) }} >
+ <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/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 9f6af1bde..a87b0e466 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -1,7 +1,13 @@
+$light-blue: #AEDDF8;
+$dark-blue: #5B9FDD;
+$light-background: #ececec;
+
.presBox-cont {
position: absolute;
+ display: block;
pointer-events: inherit;
z-index: 2;
+ font-family: Roboto;
box-shadow: #AAAAAA .2vw .2vw .4vw;
width: 100%;
min-width: 20px;
@@ -12,75 +18,837 @@
transition: 0.7s opacity ease;
.presBox-listCont {
- position: absolute;
+ position: relative;
height: calc(100% - 25px);
width: 100%;
+ margin-top: 3px;
+ }
+
+ .presBox-toolbar-dropdown {
+ border-radius: 5px;
+ background-color: white;
+ transform: translate(8px, -5px);
+ box-shadow: 1px 1px 4px 4px rgba(0, 0, 0, 0.25);
+ z-index: 1000;
+ width: calc(100% - 50px);
+ height: max-content;
+ justify-self: center;
+ letter-spacing: normal;
+ height: max-content;
+ font-weight: 500;
+ position: relative;
+ font-size: 13;
}
- .presBox-buttons {
+ .presBox-toolbar {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ height: 30px;
width: 100%;
- background: gray;
- padding-top: 5px;
- padding-bottom: 5px;
+ color: white;
+ background-color: #323232;
+
+ .toolbar-button {
+ margin-left: 10px;
+ margin-right: 10px;
+ letter-spacing: 0;
+ display: flex;
+ align-items: center;
+ transition: 0.5s;
+ }
+
+ .toolbar-button.active {
+ color: $light-blue;
+ }
+
+ .toolbar-transitionButtons {
+ display: block;
+
+ .toolbar-transition {
+ display: flex;
+ font-size: 10;
+ width: 100;
+ background-color: rgba(0, 0, 0, 0);
+ min-width: max-content;
+
+ .toolbar-icon {
+ margin-right: 5px;
+ }
+ }
+ }
+ }
+
+ .toolbar-moreInfo {
+ position: absolute;
+ right: 5px;
+ display: flex;
+ width: max-content;
+ height: 25px;
+ justify-content: center;
+ transform: rotate(90deg);
+ align-items: center;
+ transition: 0.7s ease;
+
+ .toolbar-moreInfoBall {
+ width: 4px;
+ height: 4px;
+ border-radius: 100%;
+ background-color: white;
+ margin: 1px;
+ position: relative;
+ }
+ }
+
+ .toolbar-moreInfo.active {
+ transform: rotate(0deg);
+ }
+
+ .toolbar-divider {
+ border-left: solid #ffffff70 0.5px;
+ height: 20px;
+ }
+}
+
+.dropdown {
+ font-size: 10;
+ margin-left: 5px;
+ color: darkgrey;
+ transition: 0.5s ease;
+}
+
+.dropdown.active {
+ transform: rotate(180deg);
+ color: $light-blue;
+ opacity: 0.8;
+}
+
+.presBox-ribbon {
+ position: relative;
+ display: inline;
+ font-family: Roboto;
+ color: black;
+ z-index: 100;
+ transition: 0.7s;
+
+ .ribbon-doubleButton {
+ display: inline-flex;
+ }
+
+ .presBox-reactiveGrid {
display: grid;
- grid-column-end: 4;
- grid-column-start: 1;
+ justify-content: flex-start;
+ align-items: center;
+ grid-template-columns: repeat(auto-fit, 70px);
+ }
- .presBox-viewPicker {
- height: 25;
+ .ribbon-property {
+ font-size: 11;
+ font-weight: 200;
+ height: 20;
+ background-color: #ececec;
+ color: black;
+ border: solid 1px black;
+ display: flex;
+ margin-left: 5px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ margin-right: 5px;
+ width: max-content;
+ justify-content: center;
+ align-items: center;
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+
+ .presBox-subheading {
+ font-size: 11;
+ font-weight: 400;
+ margin-top: 10px;
+ }
+
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
+ .toolbar-slider {
+ margin-top: 5px;
position: relative;
- display: inline-block;
- grid-column: 1/2;
- min-width: 15px;
+ align-self: center;
+ justify-self: left;
+ overflow: hidden;
+ width: 100%;
+ height: 10px;
+ border-radius: 10px;
+ -webkit-appearance: none;
+ background-color: #ececec;
}
- select {
- background: #323232;
- color: white;
+ .toolbar-slider:focus {
+ outline: none;
}
- .presBox-button {
- margin-right: 2.5%;
- margin-left: 2.5%;
- height: 25px;
- border-radius: 5px;
+ .toolbar-slider::-webkit-slider-runnable-track {
+ height: 10px;
+ -webkit-appearance: none;
+ margin-top: -1px;
+ }
+
+ .toolbar-slider::-webkit-slider-thumb {
+ width: 10px;
+ -webkit-appearance: none;
+ height: 10px;
+ cursor: ew-resize;
+ background: #5b9ddd;
+ box-shadow: -100vw 0 0 100vw #aedef8;
+ }
+ }
+
+ .slider-headers {
+ position: relative;
+ display: grid;
+ justify-content: space-between;
+ width: 100%;
+ height: max-content;
+ grid-template-columns: auto auto auto;
+ grid-template-rows: max-content;
+ font-weight: 100;
+ margin-top: 3px;
+ font-size: 10px;
+ }
+
+ .slider-value {
+ top: -20;
+ color: #2f86a2;
+ position: absolute;
+ }
+
+ .slider-value.none,
+ .slider-headers.none,
+ .toolbar-slider.none {
+ display: none;
+ }
+
+ .dropdown-header {
+ padding-bottom: 10px;
+ font-weight: 800;
+ text-align: center;
+ font-size: 16;
+ width: 90%;
+ color: black;
+ transform: translate(5%, 0px);
+ border-bottom: solid 2px darkgrey;
+ }
+
+
+ .ribbon-textInput {
+ border-radius: 2px;
+ height: 20px;
+ font-size: 11.5;
+ font-weight: 100;
+ align-self: center;
+ justify-self: left;
+ margin-top: 5px;
+ padding-left: 10px;
+ background-color: $light-background;
+ border: solid 1px black;
+ min-width: 80px;
+ max-width: 200px;
+ width: 100%;
+ }
+
+ .ribbon-frameSelector {
+ border: black solid 1px;
+ width: 60px;
+ height: 20px;
+ margin-top: 5px;
+ display: grid;
+ grid-template-columns: auto 27px auto;
+ position: relative;
+ border-radius: 5px;
+ overflow: hidden;
+ align-items: center;
+ justify-self: left;
+
+ .fwdKeyframe,
+ .backKeyframe {
+ cursor: pointer;
+ position: relative;
+ height: 100%;
+ background: $light-background;
display: flex;
align-items: center;
- background: #323232;
+ justify-content: center;
+ text-align: center;
+ color: black;
+ }
+
+ .numKeyframe {
+ font-size: 10;
+ font-weight: 600;
+ position: relative;
+ color: black;
+ display: flex;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+
+ .ribbon-final-box {
+ align-self: flex-start;
+ justify-self: center;
+ display: grid;
+ margin-top: 10px;
+ grid-template-rows: auto auto;
+ /* padding-left: 10px; */
+ /* padding-right: 10px; */
+ letter-spacing: normal;
+ min-width: max-content;
+ width: 100%;
+ font-size: 13;
+ font-weight: 500;
+ position: relative;
+
+
+ .ribbon-final-button {
+ position: relative;
+ font-size: 11;
+ font-weight: normal;
+ letter-spacing: normal;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 5px;
+ height: 25px;
color: white;
+ width: 100%;
+ max-width: 120;
+ padding-left: 10;
+ padding-right: 10;
+ border-radius: 10px;
+ background-color: #979797;
+ }
+
+ .ribbon-final-button-hidden {
+ position: relative;
+ font-size: 11;
+ font-weight: normal;
+ letter-spacing: normal;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 5px;
+ height: 25px;
+ color: lightgrey;
+ width: 100%;
+ max-width: 120;
+ padding-left: 10;
+ padding-right: 10;
+ border-radius: 10px;
+ background-color: black;
+ }
+ }
+
+ .selectedList {
+ display: block;
+ min-width: 50;
+ max-width: 120;
+ height: 70;
+ overflow-y: scroll;
+
+ .selectedList-items {
+ font-size: 7;
+ font-weight: normal;
+ }
+ }
+
+ .ribbon-button {
+ font-size: 10.5;
+ font-weight: 200;
+ height: 20;
+ background-color: $light-background;
+ border: solid 1px black;
+ display: flex;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ border-radius: 5px;
+ margin-right: 5px;
+ width: max-content;
+ justify-content: center;
+ align-items: center;
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+
+ .ribbon-button.active {
+ background-color: #aedef8;
+ }
+
+ .ribbon-button:hover {
+ background-color: lightgrey;
+ }
+
+ svg.svg-inline--fa.fa-thumbtack.fa-w-12.toolbar-thumbtack {
+ right: 40;
+ position: absolute;
+ transform: rotate(45deg);
+ }
+
+ .ribbon-box {
+ display: grid;
+ grid-template-rows: max-content auto;
+ justify-self: center;
+ margin-top: 10px;
+ /* padding-left: 10px; */
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: 100%;
+ /* max-width: 100%; */
+ height: max-content;
+ font-weight: 500;
+ position: relative;
+ font-size: 13;
+ padding-bottom: 10px;
+ border-bottom: solid 1px darkgrey;
- svg {
- margin: auto;
+ .presBox-dropdown:hover {
+ border: solid 1px #378AD8;
+ border-bottom-left-radius: 0px;
+
+ .presBox-dropdownOption {
+ font-size: 11;
+ display: block;
+ padding-left: 10px;
+ padding-right: 5px;
+ padding-top: 3;
+ padding-bottom: 3;
+ }
+
+ .presBox-dropdownOption:hover {
+ position: relative;
+ background-color: lightgrey;
+ }
+
+ .presBox-dropdownOption.active {
+ position: relative;
+ background-color: #aedef8;
+ }
+
+ .presBox-dropdownOptions {
+ position: absolute;
+ top: 24px;
+ left: -1px;
+ z-index: 200;
+ width: 85%;
+ min-width: max-content;
+ display: block;
+ background: #FFFFFF;
+ border: 0.5px solid #979797;
+ box-sizing: border-box;
+ box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
+ }
+
+ .presBox-dropdownIcon {
+ color: #378AD8;
}
}
- .collectionViewBaseChrome-viewPicker {
- min-width: 50;
- width: 5%;
+ .presBox-dropdown {
+ display: grid;
+ grid-template-columns: auto 20%;
+ position: relative;
+ border: solid 1px black;
+ background-color: $light-background;
+ border-radius: 5px;
+ font-size: 10;
height: 25;
+ padding-left: 5px;
+ align-items: center;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ font-weight: 200;
+ width: 100%;
+ min-width: max-content;
+ max-width: 200px;
+ overflow: visible;
+
+ .presBox-dropdownOptions {
+ display: none;
+ }
+
+ .presBox-dropdownIcon {
+ position: relative;
+ color: black;
+ align-self: center;
+ justify-self: center;
+ margin-right: 2px;
+ }
+ }
+ }
+}
+
+.presBox-ribbon.active {
+ display: grid;
+ grid-template-columns: auto auto auto auto auto;
+ grid-template-rows: 100%;
+ height: 100px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ border: solid 1px black;
+ // overflow: auto;
+
+ ::-webkit-scrollbar {
+ -webkit-appearance: none;
+ height: 3px;
+ width: 8px;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ border-radius: 2px;
+ }
+}
+
+.dropdown-play-button {
+ font-size: 12;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ text-align: left;
+ justify-content: left;
+}
+
+.dropdown-play-button:hover {
+ background-color: lightgrey;
+}
+
+.presBox-button-left {
+ position: relative;
+ align-self: flex-start;
+ justify-self: flex-start;
+ width: 80%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ padding-left: 7px;
+ padding-right: 7px;
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.presBox-button-right {
+ position: relative;
+ text-align: center;
+ border-left: solid 1px darkgrey;
+ width: 20%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ padding-left: 7px;
+ padding-right: 7px;
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.presBox-button-right.active {
+ background-color: #223063;
+ border: #aedcf6 solid 1px;
+ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.8);
+}
+
+.dropdown-play {
+ right: 0px;
+ top: calc(100% + 2px);
+ display: none;
+ border-radius: 5px;
+ width: max-content;
+ min-height: 20px;
+ height: max-content;
+ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.8);
+ z-index: 200;
+ background-color: white;
+ color: black;
+ position: absolute;
+ overflow: hidden;
+}
+
+.dropdown-play.active {
+ display: block;
+}
+
+.open-layout {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transform: translate(0px, -1px);
+ background-color: $light-background;
+ width: 40px;
+ height: 15px;
+ align-self: center;
+ justify-self: center;
+ border: solid 1px black;
+ border-top: 0px;
+ border-bottom-right-radius: 7px;
+ border-bottom-left-radius: 7px;
+}
+
+.layout-container {
+ padding: 5px;
+ display: grid;
+ background-color: $light-background;
+ grid-template-columns: repeat(auto-fit, minmax(90px, 100px));
+ width: 100%;
+ border: solid 1px black;
+ min-width: 100px;
+ overflow: hidden;
+
+ .layout:hover {
+ border: solid 2px #5c9edd;
+ }
+
+ .layout {
+ align-self: center;
+ justify-self: center;
+ margin-top: 5;
+ margin-bottom: 5;
+ position: relative;
+ height: 55px;
+ min-width: 90px;
+ width: 90px;
+ overflow: hidden;
+ background-color: white;
+ border: solid darkgrey 1px;
+ display: grid;
+ grid-template-rows: auto;
+ align-items: center;
+ text-align: center;
+
+ .title {
+ position: relative;
+ align-self: end;
+ padding-left: 3px;
+ margin-left: 3px;
+ margin-right: 3px;
+ height: 13;
+ font-size: 12;
+ display: flex;
+ background-color: #f1efec;
+ }
+
+ .subtitle {
+ align-self: flex-start;
+ position: relative;
+ padding-left: 3px;
+ margin-left: 3px;
+ margin-right: 3px;
+ font-weight: 400;
+ height: 13;
+ font-size: 9;
+ display: flex;
+ background-color: #f1efec;
+ }
+
+ .content {
position: relative;
- display: inline-block;
+ font-weight: 200;
+ align-self: flex-start;
+ padding-left: 3px;
+ margin-left: 3px;
+ margin-right: 3px;
+ height: 13;
+ font-size: 10;
+ display: flex;
+ background-color: #f1efec;
+ height: 33;
+ text-align: left;
+ font-size: 8px;
}
}
+}
+
+.presBox-buttons {
+ position: relative;
+ width: 100%;
+ background: gray;
+ min-height: 35px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ display: grid;
+ grid-template-columns: auto auto;
+
+ .presBox-viewPicker {
+ height: 25;
+ position: relative;
+ display: inline-block;
+ grid-column: 1;
+ border-radius: 5px;
+ min-width: 15px;
+ max-width: 100px;
+ left: 8px;
+ }
- .presBox-backward,
- .presBox-forward {
- width: 25px;
+ .presBox-presentPanel {
+ display: flex;
+ justify-self: end;
+ width: 100%;
+ max-width: 300px;
+ min-width: 150px;
+ }
+
+
+
+ select {
+ background: #323232;
+ color: white;
+ }
+
+ .presBox-button {
+ height: 25px;
border-radius: 5px;
- top: 50%;
+ display: none;
+ justify-content: center;
+ align-content: center;
+ align-items: center;
+ text-align: center;
+ letter-spacing: normal;
+ width: inherit;
+ background: #323232;
+ color: white;
+ }
+
+ .presBox-button.active {
+ display: flex;
+ }
+
+ .presBox-button.active:hover {
+ background-color: #233163;
+ }
+
+ .presBox-button.edit {
+ display: flex;
+ max-width: 25px;
+ }
+
+ .presBox-button.present {
+ display: flex;
+ width: max-content;
position: absolute;
- display: inline-block;
+ right: 10px;
+
+ .present-icon {
+ margin-right: 7px;
+ }
}
- .presBox-backward {
- left: 5;
+
+ .miniPresOverlay {
+ background-color: #323232;
+ color: white;
+ border-radius: 5px;
+ grid-template-rows: 100%;
+ height: 25;
+ width: max-content;
+ min-width: max-content;
+ justify-content: space-evenly;
+ align-items: center;
+ display: flex;
+ position: absolute;
+ right: 10px;
+ transition: all 0.2s;
+
+ .miniPres-button-text {
+ display: flex;
+ height: 20;
+ width: max-content;
+ font-family: Roboto;
+ font-weight: 400;
+ margin-left: 3px;
+ margin-right: 3px;
+ padding-right: 3px;
+ padding-left: 3px;
+ letter-spacing: normal;
+ border-radius: 5px;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ }
+
+ .miniPres-divider {
+ width: 0.5px;
+ height: 80%;
+ border-right: solid 1px #5a5a5a;
+ }
+
+ .miniPres-button-frame {
+ justify-self: center;
+ align-self: center;
+ align-items: center;
+ display: grid;
+ grid-template-columns: auto auto auto;
+ justify-content: space-around;
+ font-size: 11;
+ margin-left: 7;
+ width: 30;
+ height: 85%;
+ background-color: rgba(91, 157, 221, 0.4);
+ border-radius: 5px;
+ }
+
+ .miniPres-button {
+ display: flex;
+ height: 20;
+ min-width: 20;
+ margin-left: 3px;
+ margin-right: 3px;
+ border-radius: 100%;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ }
+
+ .miniPres-button:hover {
+ background-color: #5a5a5a;
+ }
+
+ .miniPres-button-text:hover {
+ background-color: #5a5a5a;
+ }
}
- .presBox-forward {
- right: 5;
+
+
+ .collectionViewBaseChrome-viewPicker {
+ min-width: 50;
+ width: 5%;
+ height: 25;
+ position: relative;
+ display: inline-block;
+ left: 8px;
}
}
+.presBox-backward,
+.presBox-forward {
+ width: 25px;
+ border-radius: 5px;
+ top: 50%;
+ position: absolute;
+ display: inline-block;
+}
+
+.presBox-backward {
+ left: 5;
+}
+
+.presBox-forward {
+ right: 5;
+}
+
// CSS adjusted for mobile devices
@media only screen and (max-device-width: 480px) {
.presBox-cont .presBox-buttons {
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index a304ced18..502fd51f3 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1,25 +1,32 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, DocCastAsync } from "../../../fields/Doc";
+import { Doc, DocListCast, DocCastAsync, WidthSym } from "../../../fields/Doc";
import { InkTool } from "../../../fields/InkField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { returnFalse, returnOne } from "../../../Utils";
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
+import { returnFalse, returnOne, numberRange, returnTrue } from "../../../Utils";
import { documentSchema } from "../../../fields/documentSchemas";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
+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 } from "../../../fields/Schema";
-import { Docs } from "../../documents/Documents";
+import { makeInterface, listSpec } from "../../../fields/Schema";
+import { Docs, DocUtils } from "../../documents/Documents";
import { PrefetchProxy } from "../../../fields/Proxy";
import { ScriptField } from "../../../fields/ScriptField";
import { Scripting } from "../../util/Scripting";
-import { InkingStroke } from "../InkingStroke";
+import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
+import { List } from "../../../fields/List";
+import { Tooltip } from "@material-ui/core";
+import { CollectionFreeFormViewChrome } from "../collections/CollectionMenu";
+import { actionAsync } from "mobx-utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { AudioBox } from "./AudioBox";
type PresBoxSchema = makeInterface<[typeof documentSchema]>;
const PresBoxDocument = makeInterface(documentSchema);
@@ -27,218 +34,326 @@ const PresBoxDocument = makeInterface(documentSchema);
@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+ static Instance: PresBox;
+
@observable _isChildActive = false;
+ @observable _moveOnFromAudio: boolean = true;
+ @observable _presTimer!: NodeJS.Timeout;
+
+ @observable _selectedArray: Doc[] = [];
+ @observable _sortedSelectedArray: Doc[] = [];
+ @observable _eleArray: HTMLElement[] = [];
+ @observable _dragArray: HTMLElement[] = [];
+
+ @observable private transitionTools: boolean = false;
+ @observable private newDocumentTools: boolean = false;
+ @observable private progressivizeTools: boolean = false;
+ @observable private moreInfoTools: boolean = false;
+ @observable private playTools: boolean = false;
+ @observable private presentTools: boolean = false;
+ @observable private pathBoolean: boolean = false;
+ @observable private expandBoolean: boolean = false;
+
@computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
@computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); }
@computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); }
constructor(props: any) {
super(props);
+ PresBox.Instance = this;
if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations.
Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
- title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data"
+ title: "pres element template", backgroundColor: "transparent", _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
}));
// this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
// this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
// the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data
- // stored on each pres element.
+ // stored on each pres element.
(this.presElement as Doc).lookupField = ScriptField.MakeFunction("lookupPresBoxField(container, field, data)",
{ field: "string", data: Doc.name, container: Doc.name });
}
this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
}
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else if (PresBox.Instance._selectedArray.length) {
+ return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+ } else { return undefined; }
+ }
+ @computed get isPres(): boolean {
+ if (this.selectedDoc?.type === DocumentType.PRES) {
+ document.addEventListener("keydown", this.keyEvents, true);
+ return true;
+ } else {
+ document.removeEventListener("keydown", this.keyEvents, true);
+ return false;
+ }
+ }
+ @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
componentDidMount() {
this.rootDoc.presBox = this.rootDoc;
this.rootDoc._forceRenderEngine = "timeline";
this.rootDoc._replacedChrome = "replaced";
+ this.layoutDoc.presStatus = "edit";
+ this.layoutDoc._gridGap = 5;
+ }
+
+ updateCurrentPresentation = () => {
+ Doc.UserDoc().activePresentation = this.rootDoc;
}
- updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc;
+ /**
+ * Called when the user moves to the next slide in the presentation trail.
+ */
@undoBatch
@action
next = () => {
this.updateCurrentPresentation();
- const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+ 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);
- if (lastFrame !== undefined && curFrame < lastFrame) {
+ let internalFrames: boolean = false;
+ if (presTargetDoc.presProgressivize || presTargetDoc.zoomProgressivize || presTargetDoc.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;
- }
- else if (this.childDocs[this.itemIndex + 1] !== undefined) {
- let nextSelected = this.itemIndex + 1;
+ if (presTargetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(presTargetDoc, currentFrame);
+ if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+ if (presTargetDoc.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc);
+ // Case 2: Audio or video therefore wait to play the audio or video before moving on
+ } else if ((presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio) {
+ 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;
this.gotoDocument(nextSelected, this.itemIndex);
-
- for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) {
- if (!this.childDocs[nextSelected].groupButton) {
- break;
- } else {
- this.gotoDocument(nextSelected, this.itemIndex);
- }
- }
+ const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null);
+ if (activeNext && targetNext.type === DocumentType.AUDIO && activeNext.playAuto) {
+ } else this._moveOnFromAudio = false;
}
}
+ /**
+ * Called when the user moves back
+ * Design choice: If there are frames within the presentation, moving back will not
+ * got back through the frames but instead directly to the next point in the presentation.
+ */
@undoBatch
@action
back = () => {
this.updateCurrentPresentation();
const docAtCurrent = this.childDocs[this.itemIndex];
if (docAtCurrent) {
- //check if any of the group members had used zooming in including the current document
- //If so making sure to zoom out, which goes back to state before zooming action
let prevSelected = this.itemIndex;
- let didZoom = docAtCurrent.zoomButton;
- for (; !didZoom && prevSelected > 0 && this.childDocs[prevSelected].groupButton; prevSelected--) {
- didZoom = this.childDocs[prevSelected].zoomButton;
- }
prevSelected = Math.max(0, prevSelected - 1);
-
this.gotoDocument(prevSelected, this.itemIndex);
}
}
- /**
- * This is the method that checks for the actions that need to be performed
- * after the document has been presented, which involves 3 button options:
- * Hide Until Presented, Hide After Presented, Fade After Presented
- */
- showAfterPresented = (index: number) => {
- this.updateCurrentPresentation();
- this.childDocs.forEach((doc, ind) => {
- const presTargetDoc = doc.presentationTargetDoc as Doc;
- //the order of cases is aligned based on priority
- if (doc.presHideTillShownButton && ind <= index) {
- presTargetDoc.opacity = 1;
- }
- if (doc.presHideAfterButton && ind < index) {
- presTargetDoc.opacity = 0;
- }
- if (doc.presFadeButton && ind < index) {
- presTargetDoc.opacity = 0.5;
- }
- });
- }
-
- /**
- * This is the method that checks for the actions that need to be performed
- * before the document has been presented, which involves 3 button options:
- * Hide Until Presented, Hide After Presented, Fade After Presented
- */
- hideIfNotPresented = (index: number) => {
+ //The function that is called when a document is clicked or reached through next or back.
+ //it'll also execute the necessary actions if presentation is playing.
+ public gotoDocument = action((index: number, fromDoc: number) => {
this.updateCurrentPresentation();
- this.childDocs.forEach((key, ind) => {
- //the order of cases is aligned based on priority
- const presTargetDoc = key.presentationTargetDoc as Doc;
- if (key.hideAfterButton && ind >= index) {
- presTargetDoc.opacity = 1;
- }
- if (key.fadeButton && ind >= index) {
- presTargetDoc.opacity = 1;
- }
- if (key.hideTillShownButton && ind > index) {
- presTargetDoc.opacity = 0;
+ Doc.UnBrushAllDocs();
+ if (index >= 0 && index < this.childDocs.length) {
+ this.rootDoc._itemIndex = index;
+ const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+ if (presTargetDoc?.lastFrame !== undefined) {
+ presTargetDoc.currentFrame = 0;
}
- });
- }
+ this.navigateToElement(this.childDocs[index]); //Handles movement to element
+ this._selectedArray = [this.childDocs[index]]; //Update selected array
+ this.onHideDocument(); //Handles hide after/before
+ }
+ });
/**
* This method makes sure that cursor navigates to the element that
- * has the option open and last in the group. If not in the group, and it has
- * te option open, navigates to that element.
+ * has the option open and last in the group.
+ * Design choice: If the next document is not in presCollection or
+ * presCollection itself then if there is a presCollection it will add
+ * a new tab. If presCollection is undefined it will open the document
+ * on the right.
*/
- navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
- this.updateCurrentPresentation();
- let docToJump = curDoc;
- let willZoom = false;
-
- const presDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
- let nextSelected = presDocs.indexOf(curDoc);
- const currentDocGroups: Doc[] = [];
- for (; nextSelected < presDocs.length - 1; nextSelected++) {
- if (!presDocs[nextSelected + 1].groupButton) {
- break;
- }
- currentDocGroups.push(presDocs[nextSelected]);
- }
+ navigateToElement = async (curDoc: Doc) => {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const srcContext = await DocCastAsync(targetDoc.context);
+ const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
+ const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined;
+ this.turnOffEdit();
- currentDocGroups.forEach((doc: Doc, index: number) => {
- if (doc.presNavButton) {
- docToJump = doc;
- willZoom = false;
- }
- if (doc.presZoomButton) {
- docToJump = doc;
- willZoom = true;
+ if (this.itemIndex >= 0) {
+ if (targetDoc) {
+ if (srcContext) this.layoutDoc.presCollection = srcContext;
+ } else if (targetDoc) this.layoutDoc.presCollection = targetDoc;
+ }
+ if (collectionDocView) {
+ if (srcContext && srcContext !== presCollection) {
+ // Case 1: new srcContext inside of current collection so add a new tab to the current pres collection
+ collectionDocView.props.addDocTab(srcContext, "inPlace");
}
- });
+ }
+ this.updateCurrentPresentation();
+ const docToJump = curDoc;
+ const willZoom = false;
//docToJump stayed same meaning, it was not in the group or was the last element in the group
- const aliasOf = await DocCastAsync(docToJump.aliasOf);
- const srcContext = aliasOf && await DocCastAsync(aliasOf.context);
- if (docToJump === curDoc) {
+ if (targetDoc.zoomProgressivize && this.rootDoc.presStatus !== 'edit') {
+ this.zoomProgressivizeNext(targetDoc);
+ } else if (docToJump === curDoc) {
//checking if curDoc has navigation open
- const target = (await DocCastAsync(curDoc.presentationTargetDoc)) || curDoc;
- if (curDoc.presNavButton && target) {
- DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext);
- } else if (curDoc.presZoomButton && target) {
+ if (curDoc.presNavButton && targetDoc) {
+ await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext);
+ } else if (curDoc.presZoomButton && targetDoc) {
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext);
+ await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext);
}
} else {
//awaiting jump so that new scale can be found, since jumping is async
- const presTargetDoc = await DocCastAsync(docToJump.presentationTargetDoc);
- presTargetDoc && await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext);
+ targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext);
+ }
+ // After navigating to the document, if it is added as a presPinView then it will
+ // 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 openDocument is selected then it should open the document for the user
+ if (collectionDocView && activeItem.openDocument) {
+ collectionDocView.props.addDocTab(activeItem, "inPlace");
+ }
+ // If website and has presWebsite data associated then on click it should
+ // go back to that specific website
+ // TODO: Add progressivize for navigating web (storing websites for given frames)
+ if (targetDoc.presWebsiteData) {
+ targetDoc.data = targetDoc.presWebsiteData;
}
}
- //The function that is called when a document is clicked or reached through next or back.
- //it'll also execute the necessary actions if presentation is playing.
- public gotoDocument = action((index: number, fromDoc: number) => {
- this.updateCurrentPresentation();
- Doc.UnBrushAllDocs();
- if (index >= 0 && index < this.childDocs.length) {
- this.rootDoc._itemIndex = index;
- const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
- if (presTargetDoc.lastFrame !== undefined) {
- presTargetDoc.currentFrame = 0;
+ /**
+ * Uses the viewfinder to progressivize through the different views of a single collection.
+ * @param presTargetDoc: document for which internal zoom is used
+ */
+ zoomProgressivizeNext = (presTargetDoc: Doc) => {
+ const srcContext = Cast(presTargetDoc.context, Doc, null);
+ const docView = DocumentManager.Instance.getDocumentView(presTargetDoc);
+ const vfLeft: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-left-indexed"]);
+ const vfWidth: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-width-indexed"]);
+ const vfTop: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-top-indexed"]);
+ const vfHeight: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-height-indexed"]);
+ // Case 1: document that is not a Golden Layout tab
+ if (srcContext) {
+ const srcDocView = DocumentManager.Instance.getDocumentView(srcContext);
+ if (srcDocView) {
+ const layoutdoc = Doc.Layout(presTargetDoc);
+ const panelWidth: number = srcDocView.props.PanelWidth();
+ const panelHeight: number = srcDocView.props.PanelHeight();
+ const newPanX = NumCast(presTargetDoc.x) + NumCast(layoutdoc._width) / 2;
+ const newPanY = NumCast(presTargetDoc.y) + NumCast(layoutdoc._height) / 2;
+ const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight);
+ srcContext._panX = newPanX + (vfLeft + (vfWidth / 2));
+ srcContext._panY = newPanY + (vfTop + (vfHeight / 2));
+ srcContext._viewScale = newScale;
}
+ }
+ // Case 2: document is the containing collection
+ if (docView && !srcContext) {
+ const panelWidth: number = docView.props.PanelWidth();
+ const panelHeight: number = docView.props.PanelHeight();
+ const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight);
+ presTargetDoc._panX = vfLeft + (vfWidth / 2);
+ presTargetDoc._panY = vfTop + (vfWidth / 2);
+ presTargetDoc._viewScale = newScale;
+ }
+ const resize = document.getElementById('resizable');
+ if (resize) {
+ resize.style.width = vfWidth + 'px';
+ resize.style.height = vfHeight + 'px';
+ resize.style.top = vfTop + 'px';
+ resize.style.left = vfLeft + 'px';
+ }
+ }
- if (!this.layoutDoc.presStatus) {
- this.layoutDoc.presStatus = true;
- this.startPresentation(index);
+
+ /**
+ * For 'Hide Before' and 'Hide After' buttons making sure that
+ * they are hidden each time the presentation is updated.
+ */
+ @action
+ onHideDocument = () => {
+ this.childDocs.forEach((doc, index) => {
+ const curDoc = Cast(doc, Doc, null);
+ const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
+ if (tagDoc) tagDoc.opacity = 1;
+ if (curDoc.presHideTillShownButton) {
+ if (index > this.itemIndex) {
+ tagDoc.opacity = 0;
+ } else if (!curDoc.presHideAfterButton) {
+ tagDoc.opacity = 1;
+ }
+ }
+ if (curDoc.presHideAfterButton) {
+ if (index < this.itemIndex) {
+ tagDoc.opacity = 0;
+ } else if (!curDoc.presHideTillShownButton) {
+ tagDoc.opacity = 1;
+ }
}
+ });
+ }
- this.navigateToElement(this.childDocs[index], fromDoc);
- this.hideIfNotPresented(index);
- this.showAfterPresented(index);
- }
- });
- //The function that starts or resets presentaton functionally, depending on status flag.
- startOrResetPres = () => {
+ //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc
+ @undoBatch
+ @action
+ startAutoPres = (startSlide: number) => {
this.updateCurrentPresentation();
- if (this.layoutDoc.presStatus) {
- this.resetPresentation();
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ if (this.layoutDoc.presStatus === "auto") {
+ if (this._presTimer) clearInterval(this._presTimer);
+ this.layoutDoc.presStatus = "manual";
} else {
- this.layoutDoc.presStatus = true;
- this.startPresentation(0);
- this.gotoDocument(0, this.itemIndex);
+ this.layoutDoc.presStatus = "auto";
+ this.startPresentation(startSlide);
+ this.gotoDocument(startSlide, this.itemIndex);
+ this._presTimer = setInterval(() => {
+ if (this.itemIndex + 1 < this.childDocs.length) this.next();
+ else {
+ clearInterval(this._presTimer);
+ this.layoutDoc.presStatus = "manual";
+ }
+ }, targetDoc.presDuration ? NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition) : 2000);
}
}
//The function that resets the presentation by removing every action done by it. It also
//stops the presentaton.
+ // TODO: Ensure resetPresentation is called when the presentation is closed
resetPresentation = () => {
this.updateCurrentPresentation();
- this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1);
this.rootDoc._itemIndex = 0;
- this.layoutDoc.presStatus = false;
}
- //The function that starts the presentation, also checking if actions should be applied
- //directly at start.
+ @action togglePath = () => this.pathBoolean = !this.pathBoolean;
+ @action toggleExpand = () => this.expandBoolean = !this.expandBoolean;
+
+ /**
+ * The function that starts the presentation at the given index, also checking if actions should be applied
+ * directly at start.
+ * @param startIndex: index that the presentation will start at
+ */
startPresentation = (startIndex: number) => {
this.updateCurrentPresentation();
this.childDocs.map(doc => {
@@ -249,82 +364,1350 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) {
presTargetDoc.opacity = 0;
}
- if (doc.presFadeButton && this.childDocs.indexOf(doc) < startIndex) {
- presTargetDoc.opacity = 0.5;
- }
});
}
- updateMinimize = action((e: React.ChangeEvent, mode: CollectionViewType) => {
- if (BoolCast(this.layoutDoc.inOverlay) !== (mode === CollectionViewType.Invalid)) {
- if (this.layoutDoc.inOverlay) {
- Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc);
+ /**
+ * The method called to open the presentation as a minimized view
+ * TODO: Look at old updateMinimize and compare...
+ */
+ updateMinimize = () => {
+ const srcContext = Cast(this.rootDoc.presCollection, Doc, null);
+ this.turnOffEdit();
+ if (srcContext) {
+ if (srcContext.miniPres) {
+ srcContext.miniPres = false;
CollectionDockingView.AddRightSplit(this.rootDoc);
- this.layoutDoc.inOverlay = false;
} else {
- const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- this.rootDoc.x = pt[0];// 500;//e.clientX + 25;
- this.rootDoc.y = pt[1];////e.clientY - 25;
+ srcContext.miniPres = true;
this.props.addDocTab?.(this.rootDoc, "close");
- Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc);
}
}
- });
+ }
+ /**
+ * Called when the user changes the view type
+ * Either 'List' (stacking) or 'Slides' (carousel)
+ */
@undoBatch
viewChanged = action((e: React.ChangeEvent) => {
//@ts-ignore
const viewType = e.target.selectedOptions[0].value as CollectionViewType;
- viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- this.updateMinimize(e, this.rootDoc._viewType = viewType);
+ // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
+ viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined);
+ this.rootDoc._viewType = viewType;
+ if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 5;
+ });
+
+ /**
+ * 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);
+ switch (movement) {
+ case 'zoom': //Pan and zoom
+ activeItem.presZoomButton = !activeItem.presZoomButton;
+ if (activeItem.presZoomButton) activeItem.presMovement = 'Zoom';
+ else activeItem.presMovement = 'None';
+ activeItem.presNavButton = false;
+ break;
+ case 'pan': //Pan
+ activeItem.presZoomButton = false;
+ activeItem.presNavButton = !activeItem.presNavButton;
+ if (activeItem.presNavButton) activeItem.presMovement = 'Pan';
+ else activeItem.presMovement = 'None';
+ break;
+ case 'jump': //Jump Cut
+ targetDoc.presTransition = 0;
+ activeItem.presSwitchButton = !activeItem.presSwitchButton;
+ if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut';
+ else activeItem.presMovement = 'None';
+ break;
+ case 'none': default:
+ activeItem.presMovement = 'None';
+ activeItem.presZoomButton = false;
+ activeItem.presNavButton = false;
+ activeItem.presSwitchButton = false;
+ break;
+ }
});
whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
+ // For dragging documents into the presentation trail
addDocumentFilter = (doc: Doc | Doc[]) => {
const docs = doc instanceof Doc ? [doc] : doc;
- docs.forEach(doc => {
- doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
- !this.childDocs.includes(doc) && (doc.presZoomButton = true);
+ docs.forEach((doc, i) => {
+ if (this.childDocs.includes(doc)) {
+ if (docs.length === i + 1) return false;
+ } else {
+ doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
+ !this.childDocs.includes(doc) && (doc.presZoomButton = true);
+ }
});
return true;
}
childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
- selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex));
getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
- panelHeight = () => this.props.PanelHeight() - 20;
+ panelHeight = () => this.props.PanelHeight() - 40;
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)
- render() {
- // const presOrderedDocs = DocListCast(this.rootDoc.presOrderedDocs);
- // if (presOrderedDocs.length != this.childDocs.length || presOrderedDocs.some((pd, i) => pd !== this.childDocs[i])) {
- // this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.slice());
- // }
- this.childDocs.slice(); // needed to insure that the childDocs are loaded for looking up fields
+ /**
+ * For sorting the array so that the order is maintained when it is dropped.
+ */
+ @action
+ sortArray = (): Doc[] => {
+ const sort: Doc[] = this._selectedArray;
+ this.childDocs.forEach((doc, i) => {
+ if (this._selectedArray.includes(doc)) {
+ sort.push(doc);
+ }
+ });
+ return sort;
+ }
+
+ /**
+ * Method to get the list of selected items in the order in which they have been selected
+ */
+ @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);
+ return (
+ <div className="selectedList-items">{index + 1}. {targetDoc.title}</div>
+ );
+ });
+ return list;
+ }
+
+ //Regular click
+ @action
+ selectElement = (doc: Doc) => {
+ this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex));
+ }
+
+ //Command click
+ @action
+ multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
+ if (!this._selectedArray.includes(doc)) {
+ this._selectedArray.push(this.childDocs[this.childDocs.indexOf(doc)]);
+ this._eleArray.push(ref);
+ this._dragArray.push(drag);
+ }
+ }
+
+ //Shift click
+ @action
+ shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
+ this._selectedArray = [];
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ if (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);
+ this._dragArray.push(drag);
+ }
+ }
+ }
+
+ // Key for when the presentaiton is active (according to Selection Manager)
+ @action
+ keyEvents = (e: KeyboardEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (e.keyCode === 27) { // Escape key
+ if (this.layoutDoc.presStatus === "edit") this._selectedArray = [];
+ else this.layoutDoc.presStatus = "edit";
+ } if ((e.metaKey || e.altKey) && e.keyCode === 65) { // Ctrl-A to select all
+ if (this.layoutDoc.presStatus === "edit") this._selectedArray = this.childDocs;
+ } if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back
+ this.back();
+ } if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next
+ this.next();
+ } if (e.keyCode === 32) { // spacebar to 'present' or autoplay
+ if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0);
+ else this.layoutDoc.presStatus = "manual";
+ }
+ if (e.keyCode === 8) { // delete selected items
+ if (this.layoutDoc.presStatus === "edit") {
+ this._selectedArray.forEach((doc, i) => {
+ this.removeDocument(doc);
+ });
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ @undoBatch
+ @action
+ viewPaths = async () => {
+ const srcContext = Cast(this.rootDoc.presCollection, Doc, null);
+ if (this.pathBoolean) {
+ if (srcContext) {
+ this.togglePath();
+ srcContext._fitToBox = false;
+ srcContext._viewType = "freeform";
+ srcContext.presPathView = false;
+ }
+ } else {
+ if (srcContext) {
+ this.togglePath();
+ srcContext._fitToBox = true;
+ srcContext._viewType = "freeform";
+ srcContext.presPathView = true;
+ }
+ }
+ const viewType = srcContext?._viewType;
+ const fit = srcContext?._fitToBox;
+ }
+
+ // Adds the index in the pres path graphically
+ @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);
+ // 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-frame">{index + 1}</div>
+ </div>);
+ // Case B: Document is not inside of the collection
+ } else {
+ order.push(
+ <div className="pathOrder" style={{ top: 0, left: 0 }}>
+ <div className="pathOrder-frame">{index + 1}</div>
+ </div>);
+ }
+ });
+ return order;
+ }
+
+ /**
+ * Method called for viewing paths which adds a single line with
+ * points at the center of each document added.
+ * Design choice: When this is called it sets _fitToBox as true so the
+ * user can have an overview of all of the documents in the collection.
+ * (Design needed for when documents in presentation trail are in another
+ * collection)
+ */
+ @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);
+ if (index = 0) pathPoints = n1x + "," + n1y;
+ else pathPoints = pathPoints + " " + n1x + "," + n1y;
+ } else {
+ if (index = 0) pathPoints = 0 + "," + 0;
+ else pathPoints = pathPoints + " " + 0 + "," + 0;
+ }
+ });
+ return (<polyline
+ points={pathPoints}
+ style={{
+ opacity: 1,
+ stroke: "#69a6db",
+ strokeWidth: 5,
+ strokeDasharray: '10 5',
+ }}
+ fill="none"
+ markerStart="url(#markerSquare)"
+ markerMid="url(#markerSquare)"
+ markerEnd="url(#markerArrow)"
+ />);
+ }
+
+ /**
+ * The function that is called on click to turn fading document after presented option on/off.
+ * It also makes sure that the option swithches from hide-after to this one, since both
+ * can't coexist.
+ */
+ @action
+ onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ activeItem.presFadeButton = !activeItem.presFadeButton;
+ if (!activeItem.presFadeButton) {
+ if (targetDoc) {
+ targetDoc.opacity = 1;
+ }
+ } else {
+ activeItem.presHideAfterButton = false;
+ if (this.rootDoc.presStatus !== "edit" && targetDoc) {
+ targetDoc.opacity = 0.5;
+ }
+ }
+ }
+
+ // Converts seconds to ms and updates presTransition
+ setTransitionTime = (number: String) => {
+ const timeInMS = Number(number) * 1000;
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ if (targetDoc) targetDoc.presTransition = timeInMS;
+ }
+
+ // Converts seconds to ms and updates presDuration
+ setDurationTime = (number: String) => {
+ const timeInMS = Number(number) * 1000;
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ if (targetDoc) targetDoc.presDuration = timeInMS;
+ }
+
+
+ @computed get transitionDropdown() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ if (activeItem && targetDoc) {
+ const transitionSpeed = targetDoc.presTransition ? String(Number(targetDoc.presTransition) / 1000) : 0.5;
+ let duration = targetDoc.presDuration ? String(Number(targetDoc.presDuration) / 1000) : 2;
+ if (targetDoc.type === DocumentType.AUDIO) duration = NumCast(targetDoc.duration);
+ const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None';
+ activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom';
+ return (
+ <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="ribbon-box">
+ Movement
+ <div className="presBox-dropdown" onPointerDown={e => e.stopPropagation()}>
+ {activeItem.presMovement}
+ <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} />
+ <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}>
+ <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>
+ </div>
+ <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"> {transitionSpeed} s </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"}`}>
+ <div className="slider-text">Fast</div>
+ <div className="slider-text">Medium</div>
+ <div className="slider-text">Slow</div>
+ </div>
+ </div>
+ <div className="ribbon-box">
+ Visibility {"&"} Duration
+ <div className="ribbon-doubleButton">
+ <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-button ${activeItem.presHideTillShownButton ? "active" : ""}`} onClick={() => activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton}>Hide before</div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-button ${activeItem.presHideAfterButton ? "active" : ""}`} onClick={() => activeItem.presHideAfterButton = !activeItem.presHideAfterButton}>Hide after</div></Tooltip>
+ </div>
+ <div className="ribbon-doubleButton" >
+ <div className="presBox-subheading">Slide Duration</div>
+ <div className="ribbon-property"> {duration} s </div>
+ </div>
+ <input type="range" step="0.1" min="0.1" max="10" value={duration} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} />
+ <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}>
+ <div className="slider-text">Short</div>
+ <div className="slider-text">Medium</div>
+ <div className="slider-text">Long</div>
+ </div>
+ </div>
+ <div className="ribbon-box">
+ Effects
+ <div className="presBox-dropdown"
+ onPointerDown={e => e.stopPropagation()}
+ >
+ {effect}
+ <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} />
+ <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}>
+ <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'None'}>None</div>
+ <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Fade'}>Fade In</div>
+ <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Flip'}>Flip</div>
+ <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Rotate'}>Rotate</div>
+ <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Bounce'}>Bounce</div>
+ <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Roll'}>Roll</div>
+ </div>
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}>
+ <div className="presBox-subheading" >Effect direction</div>
+ <div className="ribbon-property">
+ {this.effectDirection}
+ </div>
+ </div>
+ <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "left" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'left'}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "right" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'right'}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === "top" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'top'}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === "bottom" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'bottom'}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection ? "solid 2px black" : "solid 2px #5a9edd", borderRadius: "100%" }} onClick={() => targetDoc.presEffectDirection = false}></div></Tooltip>
+ </div>
+ </div>
+ <div className="ribbon-final-box">
+ <div className={this._selectedArray.length === 0 ? "ribbon-final-button" : "ribbon-final-button-hidden"} onClick={() => this.applyTo(this._selectedArray)}>
+ Apply to selected
+ </div>
+ <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}>
+ Apply to all
+ </div>
+ </div>
+ </div>
+ );
+ }
+ }
+
+ @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) {
+ case 'left': effect = "Enter from left"; break;
+ case 'right': effect = "Enter from right"; break;
+ case 'top': effect = "Enter from top"; break;
+ case 'bottom': effect = "Enter from bottom"; break;
+ default: effect = "Enter from center"; break;
+ }
+ return effect;
+ }
+
+ applyTo = (array: Doc[]) => {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ array.forEach((doc, index) => {
+ const curDoc = Cast(doc, Doc, null);
+ const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
+ if (tagDoc && targetDoc) {
+ tagDoc.presTransition = targetDoc.presTransition;
+ tagDoc.presDuration = targetDoc.presDuration;
+ tagDoc.presEffect = targetDoc.presEffect;
+ }
+ });
+ }
+
+ private inputRef = React.createRef<HTMLInputElement>();
+
+ @computed get optionsDropdown() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ if (activeItem && targetDoc) {
+ return (
+ <div>
+ <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="ribbon-box">
+ <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" style={{ backgroundColor: activeItem.playAuto ? "#aedef8" : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div>
+ <div className="ribbon-button" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : "#aedef8" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div>
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: "flex" }}>
+ <div className="ribbon-button" style={{ backgroundColor: activeItem.openDocument ? "#aedef8" : "" }} onClick={() => activeItem.openDocument = !activeItem.openDocument}>Open document</div>
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" style={{ backgroundColor: activeItem.presPinView ? "#aedef8" : "" }}
+ onClick={() => {
+ activeItem.presPinView = !activeItem.presPinView;
+ if (activeItem.presPinView) {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
+ }
+ }}>Presentation pin view</div>
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" onClick={this.progressivizeText}>Store original website</div>
+ </div>
+ </div>
+ </div>
+ </div >
+ );
+ }
+ }
+
+ @computed get newDocumentToolbarDropdown() {
+ return (
+ <div>
+ <div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="layout-container" style={{ height: 'max-content' }}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} />
+ <div className="layout" style={{ border: this.layout === 'title' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}>
+ <div className="title">Title</div>
+ <div className="subtitle">Subtitle</div>
+ </div>
+ <div className="layout" style={{ border: this.layout === 'header' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}>
+ <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
+ </div>
+ <div className="layout" style={{ border: this.layout === 'content' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}>
+ <div className="title" style={{ alignSelf: 'center' }}>Title</div>
+ <div className="content">Text goes here</div>
+ </div>
+ {/* <div className="layout" style={{ border: this.layout === 'twoColumns' ? 'solid 2px #5b9ddd' : '' }} onClick={() => runInAction(() => { this.layout = 'twoColumns'; this.createNewSlide(this.layout); })}>
+ <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div>
+ <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div>
+ <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div>
+ </div> */}
+ </div>
+ </div>
+ </div >
+ );
+ }
+
+ @observable openLayouts: boolean = false;
+ @observable addFreeform: boolean = true;
+ @observable layout: string = "";
+ @observable title: string = "";
+
+ @computed get newDocumentDropdown() {
+ return (
+ <div>
+ <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="ribbon-box">
+ Slide Title: <br></br>
+ <input className="ribbon-textInput" placeholder="..." type="text" name="fname" ref={this.inputRef} onChange={(e) => {
+ e.stopPropagation();
+ runInAction(() => this.title = e.target.value);
+ }}></input>
+ </div>
+ <div className="ribbon-box">
+ Choose type:
+ <div className="ribbon-doubleButton">
+ <div title="Text" className={'ribbon-button'} style={{ background: this.addFreeform ? "" : "#aedef8" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div>
+ <div title="Freeform" className={'ribbon-button'} style={{ background: this.addFreeform ? "#aedef8" : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div>
+ </div>
+ </div>
+ <div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}>
+ Preset layouts:
+ <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'blank')} />
+ <div className="layout" style={{ border: this.layout === 'title' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'title')}>
+ <div className="title">Title</div>
+ <div className="subtitle">Subtitle</div>
+ </div>
+ <div className="layout" style={{ border: this.layout === 'header' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'header')}>
+ <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
+ </div>
+ <div className="layout" style={{ border: this.layout === 'content' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'content')}>
+ <div className="title" style={{ alignSelf: 'center' }}>Title</div>
+ <div className="content">Text goes here</div>
+ </div>
+ <div className="layout" style={{ border: this.layout === 'twoColumns' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'twoColumns')}>
+ <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div>
+ <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div>
+ <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div>
+ </div>
+ </div>
+ <div className="open-layout" onClick={action(() => this.openLayouts = !this.openLayouts)}>
+ <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"caret-down"} size={"lg"} />
+ </div>
+ </div>
+ <div className="ribbon-final-box">
+ <div className={this.title !== "" && (this.addFreeform && this.layout !== "" || !this.addFreeform) ? "ribbon-final-button-hidden" : "ribbon-final-button"} onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}>
+ Create New Slide
+ </div>
+ </div>
+ </div>
+ </div >
+ );
+ }
+
+ createNewSlide = (layout?: string, title?: string, freeform?: boolean) => {
+ let doc = undefined;
+ 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");
+ }
+ }
+
+ createTemplate = (layout: string, input?: string) => {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ let x = 0;
+ let y = 0;
+ if (activeItem && targetDoc) {
+ x = NumCast(targetDoc.x);
+ y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20;
+ }
+ let doc = undefined;
+ const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", });
+ const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" });
+ const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" });
+ const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" });
+ const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" });
+ const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" });
+ const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" });
+ switch (layout) {
+ case 'blank':
+ doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y });
+ break;
+ case 'title':
+ doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ break;
+ case 'header':
+ doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ break;
+ case 'content':
+ doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ break;
+ case 'twoColumns':
+ doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ break;
+ default:
+ break;
+ }
+ return doc;
+ }
+
+ // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
+ @computed get presentDropdown() {
+ return (
+ <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="dropdown-play-button" onClick={this.updateMinimize}>
+ Minimize
+ </div>
+ <div className="dropdown-play-button" onClick={(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(); }))}>
+ Sidebar view
+ </div>
+ </div>
+ );
+ }
+
+ // Case in which the document has keyframes to navigate to next key frame
+ @undoBatch
+ @action
+ nextKeyframe = (tagDoc: Doc): void => {
+ const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
+ const currentFrame = Cast(tagDoc.currentFrame, "number", null);
+ if (currentFrame === undefined) {
+ tagDoc.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupScroll(tagDoc, 0);
+ CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame);
+ CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+ tagDoc.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ tagDoc.lastFrame = Math.max(NumCast(tagDoc.currentFrame), NumCast(tagDoc.lastFrame));
+ if (tagDoc.zoomProgressivize) {
+ const resize = document.getElementById('resizable');
+ if (resize) {
+ resize.style.width = this.checkList(tagDoc, tagDoc["viewfinder-width-indexed"]) + 'px';
+ resize.style.height = this.checkList(tagDoc, tagDoc["viewfinder-height-indexed"]) + 'px';
+ resize.style.top = this.checkList(tagDoc, tagDoc["viewfinder-top-indexed"]) + 'px';
+ resize.style.left = this.checkList(tagDoc, tagDoc["viewfinder-left-indexed"]) + 'px';
+ }
+ }
+ }
+
+ @undoBatch
+ @action
+ prevKeyframe = (tagDoc: Doc): void => {
+ const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
+ const currentFrame = Cast(tagDoc.currentFrame, "number", null);
+ if (currentFrame === undefined) {
+ tagDoc.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice());
+ tagDoc.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ if (tagDoc.zoomProgressivize) {
+ const resize = document.getElementById('resizable');
+ if (resize) {
+ resize.style.width = this.checkList(tagDoc, tagDoc["viewfinder-width-indexed"]) + 'px';
+ resize.style.height = this.checkList(tagDoc, tagDoc["viewfinder-height-indexed"]) + 'px';
+ resize.style.top = this.checkList(tagDoc, tagDoc["viewfinder-top-indexed"]) + 'px';
+ resize.style.left = this.checkList(tagDoc, tagDoc["viewfinder-left-indexed"]) + 'px';
+ }
+ }
+ }
+
+ /**
+ * 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);
+ let type: string = '';
+ if (activeItem) {
+ switch (targetDoc.type) {
+ case DocumentType.PDF: type = "PDF"; break;
+ case DocumentType.RTF: type = "Text node"; break;
+ case DocumentType.COL: type = "Collection"; break;
+ case DocumentType.AUDIO: type = "Audio"; break;
+ case DocumentType.VID: type = "Video"; break;
+ case DocumentType.IMG: type = "Image"; break;
+ default: type = "Other node"; break;
+ }
+ }
+ return type;
+ }
+
+ @computed get progressivizeDropdown() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+
+ if (activeItem && targetDoc) {
+ 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()}>
+ <div className="ribbon-box">
+ {this.stringType} selected
+ <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Child documents</div>
+ <div className="ribbon-button" style={{ display: activeItem.presProgressivize ? "flex" : "none", backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div>
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Internal zoom</div>
+ <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Viewfinder</div>
+ {/* <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editSnapZoomProgressivize ? "#aedef8" : "" }} onClick={this.editSnapZoomProgressivize}>Snapshot</div> */}
+ </div>
+ {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" onClick={this.progressivizeText}>Text progressivize</div>
+ <div className="ribbon-button" style={{ display: activeItem.textProgressivize ? "flex" : "none", backgroundColor: targetDoc.editTextProgressivize ? "#aedef8" : "" }} onClick={this.editTextProgressivize}>Edit</div>
+ </div> */}
+ <div className="ribbon-doubleButton" style={{ display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
+ <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll progressivize</div>
+ <div className="ribbon-button" style={{ display: activeItem.scrollProgressivize ? "flex" : "none", backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div>
+ </div>
+ </div>
+ <div className="ribbon-final-box" style={{ display: activeItem.zoomProgressivize || activeItem.scrollProgressivize || activeItem.presProgressivize || activeItem.textProgressivize ? "grid" : "none" }}>
+ Frames
+ <div className="ribbon-doubleButton">
+ <div className="ribbon-frameSelector">
+ <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc); }}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: targetDoc.editing ? "#5a9edd" : "#5a9edd" }}
+ onClick={action(() => targetDoc.editing = !targetDoc.editing)} >
+ {NumCast(targetDoc.currentFrame)}
+ </div>
+ <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc); }}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+ </div>
+ <Tooltip title={<><div className="dash-tooltip">{"Last frame"}</div></>}><div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div></Tooltip>
+ </div>
+ <div className="ribbon-button" style={{ height: 20, backgroundColor: "#5a9edd" }} onClick={() => console.log(" TODO: play frames")}>Play</div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ }
+
+ turnOffEdit = () => {
+ this.childDocs.forEach((doc) => {
+ doc.editSnapZoomProgressivize = false;
+ doc.editZoomProgressivize = false;
+ doc.editScrollProgressivize = false;
+ const targetDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ targetDoc.editSnapZoomProgressivize = false;
+ targetDoc.editZoomProgressivize = false;
+ targetDoc.editScrollProgressivize = false;
+ if (doc.type === DocumentType.WEB) {
+ doc.presWebsite = doc.data;
+ }
+ });
+ }
+
+ //Toggle whether the user edits or not
+ @action
+ editSnapZoomProgressivize = (e: React.MouseEvent) => {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ if (!targetDoc.editSnapZoomProgressivize) {
+ targetDoc.editSnapZoomProgressivize = true;
+ } else {
+ targetDoc.editSnapZoomProgressivize = false;
+ }
+
+ }
+
+ //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);
+ if (!targetDoc.editZoomProgressivize) {
+ targetDoc.editZoomProgressivize = true;
+ } else {
+ targetDoc.editZoomProgressivize = false;
+ }
+ }
+
+ //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);
+ if (!targetDoc.editScrollProgressivize) {
+ targetDoc.editScrollProgressivize = true;
+ } else {
+ targetDoc.editScrollProgressivize = false;
+ }
+ }
+
+ //Progressivize Zoom
+ @action
+ progressivizeScroll = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ activeItem.scrollProgressivize = !activeItem.scrollProgressivize;
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ targetDoc.scrollProgressivize = !targetDoc.zoomProgressivize;
+ CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc.currentFrame), true);
+ if (targetDoc.editScrollProgressivize) {
+ targetDoc.editScrollProgressivize = false;
+ targetDoc.currentFrame = 0;
+ targetDoc.lastFrame = 0;
+ }
+ }
+
+ //Progressivize Zoom
+ @action
+ progressivizeZoom = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ activeItem.zoomProgressivize = !activeItem.zoomProgressivize;
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize;
+ CollectionFreeFormDocumentView.setupZoom(targetDoc, true);
+ if (targetDoc.editZoomProgressivize) {
+ targetDoc.editZoomProgressivize = false;
+ targetDoc.currentFrame = 0;
+ targetDoc.lastFrame = 0;
+ }
+ }
+
+ //Progressivize Text nodes
+ @action
+ editTextProgressivize = (e: React.MouseEvent) => {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ targetDoc.currentFrame = targetDoc.lastFrame;
+ if (targetDoc?.editTextProgressivize) {
+ targetDoc.editTextProgressivize = false;
+ } else {
+ targetDoc.editTextProgressivize = true;
+ }
+ }
+
+ @action
+ progressivizeText = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ activeItem.presProgressivize = !activeItem.presProgressivize;
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
+ targetDoc.presProgressivize = !targetDoc.presProgressivize;
+ if (activeItem.presProgressivize) {
+ targetDoc.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
+ targetDoc.lastFrame = docs.length - 1;
+ }
+ }
+
+ //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;
+ if (targetDoc?.editProgressivize) {
+ targetDoc.editProgressivize = false;
+ } else {
+ targetDoc.editProgressivize = true;
+ }
+ }
+
+ @action
+ progressivizeChild = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
+ const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
+ if (!activeItem.presProgressivize) {
+ activeItem.presProgressivize = true;
+ targetDoc.presProgressivize = true;
+ targetDoc.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
+ targetDoc.lastFrame = docs.length - 1;
+ } else {
+ targetDoc.editProgressivize = false;
+ activeItem.presProgressivize = false;
+ targetDoc.presProgressivize = false;
+ // docs.forEach((doc, index) => {
+ // doc.appearFrame = 0;
+ // });
+ targetDoc.currentFrame = 0;
+ targetDoc.lastFrame = 0;
+ }
+ }
+
+ @action
+ checkMovementLists = (doc: Doc, xlist: any, ylist: any) => {
+ const x: List<number> = xlist;
+ const y: List<number> = ylist;
+ const tags: JSX.Element[] = [];
+ let pathPoints = ""; //List of all of the pathpoints that need to be added
+ for (let i = 0; i < x.length - 1; i++) {
+ if (y[i] || x[i]) {
+ if (i === 0) pathPoints = (x[i] - 11) + "," + (y[i] + 33);
+ else pathPoints = pathPoints + " " + (x[i] - 11) + "," + (y[i] + 33);
+ tags.push(<div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}>{i}</div>);
+ }
+ }
+ tags.push(<svg style={{ overflow: 'visible', position: 'absolute' }}><polyline
+ points={pathPoints}
+ style={{
+ position: 'absolute',
+ opacity: 1,
+ stroke: "#000000",
+ strokeWidth: 2,
+ strokeDasharray: '10 5',
+ }}
+ fill="none"
+ /></svg>);
+ return tags;
+ }
+
+ @observable
+ toggleDisplayMovement = (doc: Doc) => {
+ if (doc.displayMovement) doc.displayMovement = false;
+ else doc.displayMovement = true;
+ }
+
+ private _isDraggingTL = false;
+ private _isDraggingTR = false;
+ private _isDraggingBR = false;
+ private _isDraggingBL = false;
+ private _isDragging = false;
+ // private _drag = "";
+
+ // onPointerDown = (e: React.PointerEvent): void => {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // if (e.button === 0) {
+ // this._drag = e.currentTarget.id;
+ // console.log(this._drag);
+ // }
+ // document.removeEventListener("pointermove", this.onPointerMove);
+ // document.addEventListener("pointermove", this.onPointerMove);
+ // document.removeEventListener("pointerup", this.onPointerUp);
+ // document.addEventListener("pointerup", this.onPointerUp);
+ // }
+
+
+ //Adds event listener so knows pointer is down and moving
+ onPointerMid = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isDragging = true;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ //Adds event listener so knows pointer is down and moving
+ onPointerBR = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isDraggingBR = true;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ //Adds event listener so knows pointer is down and moving
+ onPointerBL = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isDraggingBL = true;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ //Adds event listener so knows pointer is down and moving
+ onPointerTR = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isDraggingTR = true;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ //Adds event listener so knows pointer is down and moving
+ onPointerTL = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isDraggingTL = true;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ //Removes all event listeners
+ onPointerUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isDraggingTL = false;
+ this._isDraggingTR = false;
+ this._isDraggingBL = false;
+ this._isDraggingBR = false;
+ this._isDragging = false;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ //Adjusts the value in NodeStore
+ onPointerMove = (e: PointerEvent): void => {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const tagDocView = DocumentManager.Instance.getDocumentView(targetDoc);
+ e.stopPropagation();
+ e.preventDefault();
+ const doc = document.getElementById('resizable');
+ if (doc && tagDocView) {
+
+ const scale2 = tagDocView.childScaling();
+ const scale3 = tagDocView.props.ScreenToLocalTransform().Scale;
+ const scale = NumCast(targetDoc._viewScale);
+ console.log("scale: " + NumCast(targetDoc._viewScale));
+ let height = doc.offsetHeight;
+ let width = doc.offsetWidth;
+ let top = doc.offsetTop;
+ let left = doc.offsetLeft;
+ // const newHeightB = height += (e.movementY * NumCast(targetDoc._viewScale));
+ // const newHeightT = height -= (e.movementY * NumCast(targetDoc._viewScale));
+ // const newWidthR = width += (e.movementX * NumCast(targetDoc._viewScale));
+ // const newWidthL = width -= (e.movementX * NumCast(targetDoc._viewScale));
+ // const newLeft = left += (e.movementX * NumCast(targetDoc._viewScale));
+ // const newTop = top += (e.movementY * NumCast(targetDoc._viewScale));
+ // switch (this._drag) {
+ // case "": break;
+ // case "resizer-br":
+ // doc.style.height = newHeightB + 'px';
+ // doc.style.width = newWidthR + 'px';
+ // break;
+ // case "resizer-bl":
+ // doc.style.height = newHeightB + 'px';
+ // doc.style.width = newWidthL + 'px';
+ // doc.style.left = newLeft + 'px';
+ // break;
+ // case "resizer-tr":
+ // doc.style.width = newWidthR + 'px';
+ // doc.style.height = newHeightT + 'px';
+ // doc.style.top = newTop + 'px';
+ // case "resizer-tl":
+ // doc.style.width = newWidthL + 'px';
+ // doc.style.height = newHeightT + 'px';
+ // doc.style.top = newTop + 'px';
+ // doc.style.left = newLeft + 'px';
+ // case "resizable":
+ // doc.style.top = newTop + 'px';
+ // doc.style.left = newLeft + 'px';
+ // }
+ //Bottom right
+ if (this._isDraggingBR) {
+ const newHeight = height += (e.movementY * scale);
+ doc.style.height = newHeight + 'px';
+ const newWidth = width += (e.movementX * scale);
+ doc.style.width = newWidth + 'px';
+ // Bottom left
+ } else if (this._isDraggingBL) {
+ const newHeight = height += (e.movementY * scale);
+ doc.style.height = newHeight + 'px';
+ const newWidth = width -= (e.movementX * scale);
+ doc.style.width = newWidth + 'px';
+ const newLeft = left += (e.movementX * scale);
+ doc.style.left = newLeft + 'px';
+ // Top right
+ } else if (this._isDraggingTR) {
+ const newWidth = width += (e.movementX * scale);
+ doc.style.width = newWidth + 'px';
+ const newHeight = height -= (e.movementY * scale);
+ doc.style.height = newHeight + 'px';
+ const newTop = top += (e.movementY * scale);
+ doc.style.top = newTop + 'px';
+ // Top left
+ } else if (this._isDraggingTL) {
+ const newWidth = width -= (e.movementX * scale);
+ doc.style.width = newWidth + 'px';
+ const newHeight = height -= (e.movementY * scale);
+ doc.style.height = newHeight + 'px';
+ const newTop = top += (e.movementY * scale);
+ doc.style.top = newTop + 'px';
+ const newLeft = left += (e.movementX * scale);
+ doc.style.left = newLeft + 'px';
+ } else if (this._isDragging) {
+ const newTop = top += (e.movementY * scale);
+ doc.style.top = newTop + 'px';
+ const newLeft = left += (e.movementX * scale);
+ doc.style.left = newLeft + 'px';
+ }
+ this.updateList(targetDoc, targetDoc["viewfinder-width-indexed"], width);
+ this.updateList(targetDoc, targetDoc["viewfinder-height-indexed"], height);
+ this.updateList(targetDoc, targetDoc["viewfinder-top-indexed"], top);
+ this.updateList(targetDoc, targetDoc["viewfinder-left-indexed"], left);
+ }
+ }
+
+ @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)];
+ } else {
+ x.length = NumCast(doc.currentFrame) + 1;
+ x[NumCast(doc.currentFrame)] = x[NumCast(doc.currentFrame) - 1];
+ return x[NumCast(doc.currentFrame)];
+ }
+
+ }
+
+ @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 {
+ x.length = NumCast(doc.currentFrame) + 1;
+ x[NumCast(doc.currentFrame)] = val;
+ list = x;
+ }
+ }
+
+ // scale: NumCast(targetDoc._viewScale),
+ @computed get zoomProgressivizeContainer() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ if (targetDoc) {
+ const vfLeft: number = this.checkList(targetDoc, targetDoc["viewfinder-left-indexed"]);
+ const vfWidth: number = this.checkList(targetDoc, targetDoc["viewfinder-width-indexed"]);
+ const vfTop: number = this.checkList(targetDoc, targetDoc["viewfinder-top-indexed"]);
+ const vfHeight: number = this.checkList(targetDoc, targetDoc["viewfinder-height-indexed"]);
+ return (
+ <>
+ {!targetDoc.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerMid} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}>
+ <div className='resizers'>
+ <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerTL}></div>
+ <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerTR}></div>
+ <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerBL}></div>
+ <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerBR}></div>
+ </div>
+ </div>}
+ </>
+ );
+ }
+ }
+
+ @computed get progressivizeChildDocs() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
+ const tags: JSX.Element[] = [];
+ docs.forEach((doc, index) => {
+ if (doc["x-indexed"] && doc["y-indexed"]) {
+ 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-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>
+ </div>);
+ });
+ return tags;
+ }
+
+ @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 appearFrame = Cast(doc.appearFrame, "number", null);
+ if (appearFrame === undefined) {
+ doc.appearFrame = 0;
+ }
+ doc.appearFrame = appearFrame + 1;
+ this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame));
+ }
+
+ @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 appearFrame = Cast(doc.appearFrame, "number", null);
+ if (appearFrame === undefined) {
+ doc.appearFrame = 0;
+ }
+ doc.appearFrame = Math.max(0, appearFrame - 1);
+ this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame));
+ }
+
+ @action
+ updateOpacityList = (list: any, frame: number) => {
+ const x: List<number> = list;
+ if (x && x.length >= frame) {
+ for (let i = 0; i < x.length; i++) {
+ if (i < frame) {
+ x[i] = 0;
+ } else if (i >= frame) {
+ x[i] = 1;
+ }
+ }
+ list = x;
+ } else {
+ x.length = frame + 1;
+ for (let i = 0; i < x.length; i++) {
+ if (i < frame) {
+ x[i] = 0;
+ } else if (i >= frame) {
+ x[i] = 1;
+ }
+ }
+ list = x;
+ }
+ }
+
+ @computed get moreInfoDropdown() {
+ return (<div></div>);
+ }
+
+ @computed
+ get toolbarWidth(): number {
+ const width = this.props.PanelWidth();
+ return width;
+ }
+
+ @computed get toolbar() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ return (
+ <div id="toolbarContainer" className={'presBox-toolbar'} style={{ display: this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }}>
+ <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
+ <FontAwesomeIcon icon={"plus"} />
+ <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
+ </div></Tooltip>
+ <div className="toolbar-divider" />
+ <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}>
+ <div style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3 }} className={`toolbar-button ${this.pathBoolean ? "active" : ""}`} onClick={this.childDocs.length > 1 ? this.viewPaths : undefined}>
+ <FontAwesomeIcon icon={"exchange-alt"} />
+ </div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{this.expandBoolean ? "Minimize all" : "Expand all"}</div></>}>
+ <div style={{ opacity: this.childDocs.length > 0 ? 1 : 0.3 }} className={`toolbar-button ${this.expandBoolean ? "active" : ""}`} onClick={() => { if (this.childDocs.length > 0) this.toggleExpand(); this.childDocs.forEach((doc, ind) => { if (this.expandBoolean) doc.presExpandInlineButton = true; else doc.presExpandInlineButton = false; }); }}>
+ <FontAwesomeIcon icon={"eye"} />
+ </div>
+ </Tooltip>
+ <div className="toolbar-divider" />
+ </div>
+ );
+ }
+
+ /**
+ * Top panel containes:
+ * viewPicker: The option to choose between List and Slides view for the presentaiton trail
+ * presentPanel: The button to start the presentation / open minimized view of the presentation
+ */
+ @computed get topPanel() {
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
- return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
+ return (
<div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
<select className="presBox-viewPicker"
+ style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
onPointerDown={e => e.stopPropagation()}
onChange={this.viewChanged}
value={mode}>
- <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Invalid}>Min</option>
<option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
- <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Time}>Time</option>
<option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option>
</select>
- <div className="presBox-button" title="Back" style={{ gridColumn: 2 }} onClick={this.back}>
- <FontAwesomeIcon icon={"arrow-left"} />
- </div>
- <div className="presBox-button" title={"Reset Presentation" + this.layoutDoc.presStatus ? "" : " From Start"} style={{ gridColumn: 3 }} onClick={this.startOrResetPres}>
- <FontAwesomeIcon icon={this.layoutDoc.presStatus ? "stop" : "play"} />
- </div>
- <div className="presBox-button" title="Next" style={{ gridColumn: 4 }} onClick={this.next}>
- <FontAwesomeIcon icon={"arrow-right"} />
+ <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length > 0 ? 1 : 0.3 }}>
+ <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}>
+ <div className="presBox-button-left" onClick={() => (this.childDocs.length > 0) && (this.layoutDoc.presStatus = "manual")}>
+ <FontAwesomeIcon icon={"play-circle"} />
+ <div style={{ display: this.props.PanelWidth() > 200 ? "inline-flex" : "none" }}>&nbsp; Present</div>
+ </div>
+ <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`}
+ onClick={(action(() => {
+ if (this.childDocs.length > 0) this.presentTools = !this.presentTools;
+ }))}>
+ <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} />
+ {this.presentDropdown}
+ </div>
+ </span>
+ {this.playButtons}
</div>
</div>
- <div className="presBox-listCont" >
+ );
+ }
+
+ @computed get playButtonFrames() {
+ const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
+ const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
+ return (
+ <>
+ {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}
+ </>
+ );
+ }
+
+ @computed get playButtons() {
+ // Case 1: There are still other frames and should go through all frames before going to next slide
+ return (<div className="miniPresOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}>
+ <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>
+ <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div>
+ <div className="miniPres-divider"></div>
+ <div className="miniPres-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}>
+ Slide {this.itemIndex + 1} / {this.childDocs.length}
+ {this.playButtonFrames}
+ </div>
+ <div className="miniPres-divider"></div>
+ {this.props.PanelWidth() > 250 ? <div className="miniPres-button-text" onClick={() => this.layoutDoc.presStatus = "edit"}>EXIT</div>
+ : <div className="miniPres-button" onClick={() => this.layoutDoc.presStatus = "edit"}>
+ <FontAwesomeIcon icon={"times"} />
+ </div>}
+ </div>);
+ }
+
+ render() {
+ // calling this method for keyEvents
+ this.isPres;
+ // needed to ensure that the childDocs are loaded for looking up fields
+ this.childDocs.slice();
+ const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
+ return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
+ {this.topPanel}
+ {this.toolbar}
+ {this.newDocumentToolbarDropdown}
+ <div className="presBox-listCont">
{mode !== CollectionViewType.Invalid ?
<CollectionView {...this.props}
ContainingCollectionDoc={this.props.Document}
@@ -346,9 +1729,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
- if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46;
+ if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 30 : 26;
if (field === 'presStatus') return container.presStatus;
if (field === '_itemIndex') return container._itemIndex;
if (field === 'presBox') return container;
return undefined;
-});
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 0fff0b57f..1b6056be6 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -1,41 +1,38 @@
-import React = require("react");
-import { IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from '../../../fields/FieldSymbols';
-import { makeInterface, listSpec } from "../../../fields/Schema";
-import { StrCast, Cast } from "../../../fields/Types";
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { SearchBox } from "../search/SearchBox";
-import { FieldView, FieldViewProps } from './FieldView';
-import "./QueryBox.scss";
-import { List } from "../../../fields/List";
-import { SnappingManager } from "../../util/SnappingManager";
+// import React = require("react");
+// import { IReactionDisposer } from "mobx";
+// import { observer } from "mobx-react";
+// import { documentSchema } from "../../../new_fields/documentSchemas";
+// import { Id } from '../../../new_fields/FieldSymbols';
+// import { makeInterface, listSpec } from "../../../new_fields/Schema";
+// import { StrCast, Cast } from "../../../new_fields/Types";
+// import { ViewBoxAnnotatableComponent } from '../DocComponent';
+// import { SearchBox } from "../search/SearchBox";
+// import { FieldView, FieldViewProps } from './FieldView';
+// import "./QueryBox.scss";
+// import { List } from "../../../new_fields/List";
+// import { SnappingManager } from "../../util/SnappingManager";
-type QueryDocument = makeInterface<[typeof documentSchema]>;
-const QueryDocument = makeInterface(documentSchema);
+// type QueryDocument = makeInterface<[typeof documentSchema]>;
+// const QueryDocument = makeInterface(documentSchema);
-@observer
-export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
- _docListChangedReaction: IReactionDisposer | undefined;
- componentDidMount() {
- }
+// @observer
+// export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
+// public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
+// _docListChangedReaction: IReactionDisposer | undefined;
+// componentDidMount() {
+// }
- componentWillUnmount() {
- this._docListChangedReaction?.();
- }
+// componentWillUnmount() {
+// this._docListChangedReaction?.();
+// }
- render() {
- const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging";
- return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
- <SearchBox
- id={this.props.Document[Id]}
- setSearchQuery={q => this.dataDoc.searchQuery = q}
- searchQuery={StrCast(this.dataDoc.searchQuery)}
- setSearchFileTypes={q => this.dataDoc.searchFileTypes = new List<string>(q)}
- searchFileTypes={Cast(this.dataDoc.searchFileTypes, listSpec("string"), [])}
- filterQquery={StrCast(this.dataDoc.filterQuery)} />
- </div >;
- }
-} \ No newline at end of file
+// render() {
+// const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging";
+// return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
+
+// <SearchBox Document={this.props.Document} />
+// </div >;
+// }
+// }
+
+// //<SearchBox id={this.props.Document[Id]} sideBar={side} Document={this.props.Document} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQuery={this.dataDoc.filterQuery} />
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index bc43cd473..1a5edc1d9 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -32,7 +32,7 @@ const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
private dropDisposer?: DragManager.DragDropDisposer;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
private _overlayDisposer?: () => void;
private _caretPos = 0;
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
index 89602f219..2a3dd8d2d 100644
--- a/src/client/views/nodes/TaskCompletedBox.tsx
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -1,7 +1,5 @@
import React = require("react");
import { observer } from "mobx-react";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
import "./TaskCompletedBox.scss";
import { observable, action } from "mobx";
import { Fade } from "@material-ui/core";
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index f5c8745e7..875142169 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -23,7 +23,7 @@
position: absolute;
background-color: rgba(245, 230, 95, 0.616);
}
- .webBox-container, .webBox-container-dragging {
+ .webBox-container {
transform-origin: top left;
width: 100%;
height: 100%;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index bc30b15e6..3283f568a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
import { observer } from "mobx-react";
import { Dictionary } from "typescript-collections";
import * as WebRequest from 'web-request';
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, AclAddonly, AclEdit, AclAdmin } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { HtmlField } from "../../../fields/HtmlField";
@@ -13,8 +13,8 @@ import { List } from "../../../fields/List";
import { listSpec, makeInterface } from "../../../fields/Schema";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils } from "../../../Utils";
+import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
+import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -57,13 +57,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@observable private _pressY: number = 0;
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
private _selectionReactionDisposer?: IReactionDisposer;
+ private _scrollReactionDisposer?: IReactionDisposer;
+ private _moveReactionDisposer?: IReactionDisposer;
private _keyInput = React.createRef<HTMLInputElement>();
private _longPressSecondsHack?: NodeJS.Timeout;
private _outerRef = React.createRef<HTMLDivElement>();
private _iframeRef = React.createRef<HTMLIFrameElement>();
private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
private _iframeDragRef = React.createRef<HTMLDivElement>();
- private _reactionDisposer?: IReactionDisposer;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
iframeLoaded = action((e: any) => {
@@ -76,22 +77,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc._scrollTop);
iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft);
}
- this._reactionDisposer?.();
- this._reactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
- ({ x, y }) => {
- if (y !== undefined) {
- this._outerRef.current!.scrollTop = y;
- this.layoutDoc._scrollY = undefined;
- }
- if (x !== undefined) {
- this._outerRef.current!.scrollLeft = x;
- this.layoutDoc.scrollX = undefined;
- }
- },
+ this._scrollReactionDisposer?.();
+ this._scrollReactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
+ ({ x, y }) => this.updateScroll(x, y),
{ fireImmediately: true }
);
});
+ updateScroll = (x: Opt<number>, y: Opt<number>) => {
+ if (y !== undefined) {
+ this._outerRef.current!.scrollTop = y;
+ this.layoutDoc._scrollY = undefined;
+ }
+ if (x !== undefined) {
+ this._outerRef.current!.scrollLeft = x;
+ this.layoutDoc.scrollX = undefined;
+ }
+ }
+
setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
iframedown = (e: PointerEvent) => {
this._setPreviewCursor?.(e.screenX, e.screenY, false);
@@ -106,6 +109,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
runInAction(() => this._url = urlField?.url.toString() || "");
+ this._moveReactionDisposer = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
+ () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
selected => {
@@ -140,8 +145,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
componentWillUnmount() {
+ this._moveReactionDisposer?.();
this._selectionReactionDisposer?.();
- this._reactionDisposer?.();
+ this._scrollReactionDisposer?.();
document.removeEventListener("pointerup", this.onLongPressUp);
document.removeEventListener("pointermove", this.onLongPressMove);
this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown);
@@ -529,9 +535,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@action
highlight = (color: string) => {
// creates annotation documents for current highlights
- const annotationDoc = this.makeAnnotationDocument(color);
- annotationDoc && Doc.AddDocToList(this.props.Document, this.annotationKey, annotationDoc);
- return annotationDoc;
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) ? this.makeAnnotationDocument(color) : undefined;
+ annotationDoc && this.addDocument?.(annotationDoc);
+ return annotationDoc ?? undefined;
}
/**
* This is temporary for creating annotations from highlights. It will
@@ -547,7 +554,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
clipDoc._width = this.marqueeWidth();
clipDoc._height = this.marqueeHeight();
clipDoc._scrollTop = this.marqueeY();
- const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
+ const targetDoc = Docs.Create.TextDocument("", { _width: 125, _height: 125, title: "Note linked to " + this.props.Document.title });
Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
clipDoc.rootDocument = targetDoc;
targetDoc.layoutKey = "layout";
@@ -558,8 +565,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
annotationDoc.isLinkButton = true;
- e.annoDragData.dropDocument.isPushpin = true;
- e.annoDragData.dropDocument.isLinkButton = true;
}
}
});
@@ -582,8 +587,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
else if (this._mainCont.current) {
// set marquee x and y positions to the spatially transformed position
const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width;
this._startX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
- this._startY = (e.clientY - boundingRect.top) / boundingRect.height * (this.Document._nativeHeight || 1);
+ this._startY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1);
this._marqueeHeight = this._marqueeWidth = 0;
this._marqueeing = true;
}
@@ -598,8 +604,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (this._marqueeing && this._mainCont.current) {
// transform positions and find the width and height to set the marquee to
const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width;
const curX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
- const curY = (e.clientY - boundingRect.top) / boundingRect.height * (this.Document._nativeHeight || 1);
+ const curY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1);
this._marqueeWidth = curX - this._startX;
this._marqueeHeight = curY - this._startY;
this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
@@ -651,17 +658,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
document.removeEventListener("pointermove", this.onSelectMove);
document.removeEventListener("pointerup", this.onSelectEnd);
}
-
marqueeWidth = () => this._marqueeWidth;
marqueeHeight = () => this._marqueeHeight;
marqueeX = () => this._marqueeX;
marqueeY = () => this._marqueeY;
marqueeing = () => this._marqueeing;
+ visibleHeiht = () => {
+ if (this._mainCont.current) {
+ const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const scalin = (this.Document._nativeWidth || 0) / boundingRect.width;
+ return Math.min(boundingRect.height * scalin, this.props.PanelHeight() * scalin);
+ }
+ return this.props.PanelHeight();
+ }
scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
render() {
return (<div className="webBox" ref={this._mainCont} >
<div className={`webBox-container`}
style={{
+ position: undefined,
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%",
@@ -672,7 +687,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
{this.content}
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
- width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
+ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%",
pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none"
}}
onWheel={e => e.stopPropagation()}
@@ -700,6 +715,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
annotationsKey={this.annotationKey}
NativeHeight={returnZero}
NativeWidth={returnZero}
+ VisibleHeight={this.visibleHeiht}
focus={this.props.focus}
setPreviewCursor={this.setPreviewCursor}
isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8718bf329..8ae71c035 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -190,7 +190,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;
+ alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
this.props.tbox.props.addDocTab(alias, "onRight");
}
}
@@ -205,9 +205,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
{this._fieldKey}
</span>}
- {/* <div className="dashFieldView-fieldSpan"> */}
- {this.fieldValueContent}
- {/* </div> */}
+ {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
{!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fabda4d20..b0bf54be6 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -235,7 +235,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
@@ -251,7 +250,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
this._applyingChange = true;
- (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
+ const lastmodified = "lastmodified";
+ (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))) && (this.dataDoc[lastmodified] = new DateField(new Date(Date.now())));
if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
if (!this._pause && !this.layoutDoc._timeStampOnEnter) {
@@ -305,7 +305,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// for inserting timestamps
insertTime = () => {
- let linkTime;
if (this._first) {
this._first = false;
DocListCast(this.dataDoc.links).map((l, i) => {
@@ -318,7 +317,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._linkTime = NumCast(l.anchor1_timecode);
}
- })
+ });
}
this._currentTime = Date.now();
let time;
@@ -353,7 +352,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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) {
let node = this._editorView.state.doc;
- while (node.firstChild) node = node.firstChild;
+ while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
const str = node.textContent;
const titlestr = str.substr(0, Math.min(40, str.length));
this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
@@ -375,18 +374,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
- public highlightSearchTerms = (terms: string[]) => {
+ public highlightSearchTerms = (terms: string[], alt: 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));
+ const length = res[0].length;
let tr = this._editorView.state.tr;
const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
+
+
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));
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+ 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;
+ }
+
+ }
+ else {
+
+ }
+ const index = this._searchIndex;
+
+ Doc.GetProto(this.dataDoc).searchIndex = index;
}
}
@@ -397,6 +418,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
const end = this._editorView.state.doc.nodeSize - 2;
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+
}
if (FormattedTextBox.PasteOnLoad) {
const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin");
@@ -620,6 +642,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
}, icon: "eye"
});
+ appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" });
cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
const options = cm.findByDescription("Options...");
@@ -631,6 +654,67 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._downX = this._downY = Number.NaN;
}
+ progressivizeText = () => {
+ const list = this.ProseRef?.getElementsByTagName("li");
+ const mainBulletText: string[] = [];
+ const mainBulletList: Doc[] = [];
+ if (list) {
+ 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, {
+ title: StrCast(this.rootDoc.title),
+ x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
+ _width: 400, _height: 225, _fitToBox: true,
+ });
+ this.props.addDocument?.(doc);
+ }
+
+ recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): [Doc[], number] => {
+ const mainBulletList: Doc[] = [];
+ let b = d ? d : 0;
+ let yLoc = y ? y : 0;
+ let nestCount = 0;
+ let count: string = before ? before : '';
+ const fontSize: string = (16 - (nestDepth * 2)) + 'pt';
+ const xLoc: number = (nestDepth * 20);
+ const width: number = 390 - xLoc;
+ const height: number = 55 - (nestDepth * 5);
+ Array.from(list).forEach(listItem => {
+ const mainBullets: number = Number(listItem.getAttribute("data-bulletstyle"));
+ if (mainBullets === nestDepth) {
+ if (listItem.childElementCount > 1) {
+ b++;
+ nestCount++;
+ yLoc += height;
+ count = before ? count + nestCount + "." : nestCount + ".";
+ const text = listItem.getElementsByTagName("p")[0].innerText;
+ const length = text.length;
+ const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b });
+ // yLoc += NumCast(bullet1._height);
+ mainBulletList.push(bullet1);
+ const newList = this.recursiveProgressivize(nestDepth + 1, listItem.getElementsByTagName("li"), b, yLoc, count);
+ mainBulletList.push.apply(mainBulletList, newList[0]);
+ yLoc += newList.length * (55 - ((nestDepth + 1) * 5));
+ } else {
+ b++;
+ nestCount++;
+ yLoc += height;
+ count = before ? count + nestCount + "." : nestCount + ".";
+ const text = listItem.innerText;
+ const length = text.length;
+ const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b });
+ // yLoc += NumCast(bullet1._height);
+ mainBulletList.push(bullet1);
+ }
+ }
+ });
+ return [mainBulletList, yLoc];
+ }
+
recordDictation = () => {
DictationManager.Controls.listen({
interimHandler: this.setCurrentBulletContent,
@@ -822,9 +906,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.setupEditor(this.config, this.props.fieldKey);
- this._disposers.search = reaction(() => this.rootDoc.searchMatch,
- search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
+ 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,
() => {
@@ -1077,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
+ // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
const { state: { tr }, dispatch } = this._editorView;
@@ -1094,10 +1181,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBox.SelectOnLoadChar = "";
}
- (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
+ selectOnLoad && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
- if (!this._editorView!.state.storedMarks || !this._editorView!.state.storedMarks.some(mark => mark.type === schema.marks.user_mark)) {
- this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+ if (!this._editorView!.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+ this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
}
}
getFont(font: string) {
@@ -1351,7 +1438,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.doLinkOnDeselect();
// move the richtextmenu offscreen
- if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide();
+ //if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide();
}
_lastTimedMark: Mark | undefined = undefined;
@@ -1433,7 +1520,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
render() {
TraceMobx();
- const scale = this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
+ 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
@@ -1461,6 +1548,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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"
}}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 47a4911b8..459632ec8 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -77,7 +77,8 @@ export default class RichTextMenu extends AntimodeMenu {
super(props);
RichTextMenu.Instance = this;
this._canFade = false;
- this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
+ //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
+ this.Pinned = true;
this.fontSizeOptions = [
{ mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
@@ -184,11 +185,15 @@ export default class RichTextMenu extends AntimodeMenu {
const active = this.getActiveFontStylesOnSelection();
const activeFamilies = active.activeFamilies;
const activeSizes = active.activeSizes;
+ const activeColors = active.activeColors;
+ const activeHighlights = active.activeHighlights;
this.activeListType = this.getActiveListStyle();
this.activeAlignment = this.getActiveAlignment();
this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
this.activeFontSize = !activeSizes.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "...";
+ this.activeFontColor = !activeColors.length ? "black" : activeColors.length === 1 ? String(activeColors[0]) : "...";
+ this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "...";
// update link in current selection
const targetTitle = await this.getTextLinkTargetTitle();
@@ -223,7 +228,7 @@ export default class RichTextMenu extends AntimodeMenu {
if (this.view && this.TextView.props.isSelected(true)) {
const path = (this.view.state.selection.$from as any).path;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
- if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
+ if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
return path[i].attrs.align || "left";
}
}
@@ -249,10 +254,12 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveFontStylesOnSelection() {
- if (!this.view) return { activeFamilies: [], activeSizes: [] };
+ if (!this.view) return { activeFamilies: [], activeSizes: [], activeColors: [], activeHighlights: [] };
const activeFamilies: string[] = [];
const activeSizes: string[] = [];
+ const activeColors: string[] = [];
+ const activeHighlights: string[] = [];
if (this.TextView.props.isSelected(true)) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
@@ -260,15 +267,20 @@ export default class RichTextMenu extends AntimodeMenu {
if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
ref_node.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");
+ m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight));
});
}
!activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily))));
!activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize))));
+ !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor))));
}
!activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily)));
!activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize)));
- return { activeFamilies, activeSizes };
+ !activeColors.length && (activeColors.push(StrCast(Doc.UserDoc().fontColor, "black")));
+ !activeHighlights.length && (activeHighlights.push(StrCast(Doc.UserDoc().fontHighlight, "")));
+ return { activeFamilies, activeSizes, activeColors, activeHighlights };
}
getMarksInSelection(state: EditorState<any>) {
@@ -425,10 +437,16 @@ export default class RichTextMenu extends AntimodeMenu {
}
changeFontSize = (mark: Mark, view: EditorView) => {
+ 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);
}
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);
}
@@ -490,7 +508,7 @@ export default class RichTextMenu extends AntimodeMenu {
alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph) {
+ if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks);
return false;
}
@@ -503,7 +521,7 @@ export default class RichTextMenu extends AntimodeMenu {
insetParagraph(state: EditorState<any>, dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph) {
+ if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
return false;
@@ -516,7 +534,7 @@ export default class RichTextMenu extends AntimodeMenu {
outsetParagraph(state: EditorState<any>, dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph) {
+ if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
return false;
@@ -529,8 +547,9 @@ export default class RichTextMenu extends AntimodeMenu {
indentParagraph(state: EditorState<any>, dispatch: any) {
var tr = state.tr;
+ const heading = false;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph) {
+ if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
@@ -538,14 +557,14 @@ export default class RichTextMenu extends AntimodeMenu {
}
return true;
});
- dispatch?.(tr);
+ !heading && dispatch?.(tr);
return true;
}
hangingIndentParagraph(state: EditorState<any>, dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph) {
+ if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
@@ -604,8 +623,11 @@ export default class RichTextMenu extends AntimodeMenu {
label = "No marks are currently stored";
}
+ //onPointerDown={onBrushClick}
+
const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+
+ <button className="antimodeMenu-button" style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
<FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
</button>
</Tooltip>;
@@ -614,11 +636,11 @@ export default class RichTextMenu extends AntimodeMenu {
<div className="dropdown">
<p>{label}</p>
<button onPointerDown={this.clearBrush}>Clear brush</button>
- <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
+ <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress} />
</div>;
return (
- <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
);
}
@@ -677,8 +699,9 @@ export default class RichTextMenu extends AntimodeMenu {
self.TextView.EditorView!.focus();
}
+ // onPointerDown={onColorClick}
const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button" onPointerDown={onColorClick}>
+ <button className="antimodeMenu-button color-preview-button">
<FontAwesomeIcon icon="palette" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
</button>
@@ -699,7 +722,7 @@ export default class RichTextMenu extends AntimodeMenu {
</div>;
return (
- <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
);
}
@@ -731,8 +754,9 @@ export default class RichTextMenu extends AntimodeMenu {
UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
}
+ //onPointerDown={onHighlightClick}
const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button" key="highilghter-button" onPointerDown={onHighlightClick}>
+ <button className="antimodeMenu-button color-preview-button" key="highilghter-button" >
<FontAwesomeIcon icon="highlighter" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
</button>
@@ -753,7 +777,7 @@ export default class RichTextMenu extends AntimodeMenu {
</div>;
return (
- <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
+ <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
);
}
@@ -776,7 +800,9 @@ export default class RichTextMenu extends AntimodeMenu {
const link = this.currentLink ? this.currentLink : "";
const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <div><FontAwesomeIcon icon="link" size="lg" /> </div>
+ <button className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
</Tooltip>;
const dropdownContent =
@@ -788,7 +814,8 @@ export default class RichTextMenu extends AntimodeMenu {
<button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
</div>;
- return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />;
+ return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent}
+ openDropdownOnButton={true} link={true} />;
}
async getTextLinkTargetTitle() {
@@ -827,6 +854,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);
}
@@ -874,10 +902,12 @@ export default class RichTextMenu extends AntimodeMenu {
if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
ref_node = pos.nodeBefore;
}
- else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
- ref_node = pos.nodeAfter;
+ if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+ if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) {
+ ref_node = pos.nodeAfter;
+ }
}
- else if (pos.pos > 0) {
+ if (!ref_node && pos.pos > 0) {
let skip = false;
for (let i: number = pos.pos - 1; i > 0; i--) {
this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
@@ -918,16 +948,22 @@ export default class RichTextMenu extends AntimodeMenu {
render() {
TraceMobx();
const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
- !this.collapsed ? this.getDragger() : (null),
- !this.Pinned ? (null) : <div key="frag1"> {[
- this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- <div className="richTextMenu-divider" key="divider" />
- ]}</div>,
+ //!this.collapsed ? this.getDragger() : (null),
+ // !this.Pinned ? (null) : <div key="frag1"> {[
+ // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+ // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+ // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+ // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+ // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+ // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+ // <div className="richTextMenu-divider" key="divider" />
+ // ]}</div>,
+ this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+ this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+ this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+ this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+ this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+ this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
this.createColorButton(),
this.createHighlighterButton(),
this.createLinkButton(),
@@ -955,16 +991,16 @@ export default class RichTextMenu extends AntimodeMenu {
this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
<div className="richTextMenu-divider" key="divider 5" />,]}
</div>
- <div key="collapser">
- {/* <div key="collapser">
+ {/* <div key="collapser">
+ {<div key="collapser">
<button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
<FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
</button>
- </div> */}
+ </div> }
<button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
<FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
</button>
- </div>
+ </div> */}
</div>;
return (
@@ -980,6 +1016,7 @@ interface ButtonDropdownProps {
button: JSX.Element;
dropdownContent: JSX.Element;
openDropdownOnButton?: boolean;
+ link?: boolean;
}
@observer
@@ -1022,18 +1059,10 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
render() {
return (
<div className="button-dropdown-wrapper" ref={node => this.ref = node}>
- {this.props.openDropdownOnButton ?
- <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
- {this.props.button}
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </button> :
- <>
- {this.props.button}
- <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </button>
- </>}
-
+ <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.showDropdown ? this.props.dropdownContent : (null)}
</div>
);
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ef0fead4a..dc1d8a2c8 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -317,13 +317,12 @@ export class RichTextRules {
// create an inline view of a tag stored under the '#' field
new InputRule(
- new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
+ new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
(state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- const multiple = tag.split(";");
- this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+ this.Document[DataSym]["#" + 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/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 2ce61ab58..ce784c3d9 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -40,7 +40,7 @@ export const marks: { [index: string]: MarkSpec } = {
return node.attrs.docref && node.attrs.title ?
["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
node.attrs.allLinks.length === 1 ?
- ["a", { ...node.attrs, class: linkids, dataTargetids: targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] :
+ ["a", { ...node.attrs, class: linkids, dataTargetids: targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0] :
["div", { class: "prosemirror-anchor" },
["span", { class: "prosemirror-linkBtn" },
["a", { ...node.attrs, class: linkids, dataTargetids: targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 1af821738..1616500f6 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -66,9 +66,11 @@ export const nodes: { [index: string]: NodeSpec } = {
// should hold the number 1 to 6. Parsed and serialized as `<h1>` to
// `<h6>` elements.
heading: {
- attrs: { level: { default: 1 } },
- content: "inline*",
- group: "block",
+ ...ParagraphNodeSpec,
+ attrs: {
+ ...ParagraphNodeSpec.attrs,
+ level: { default: 1 },
+ },
defining: true,
parseDOM: [{ tag: "h1", attrs: { level: 1 } },
{ tag: "h2", attrs: { level: 2 } },
@@ -76,7 +78,18 @@ export const nodes: { [index: string]: NodeSpec } = {
{ tag: "h4", attrs: { level: 4 } },
{ tag: "h5", attrs: { level: 5 } },
{ tag: "h6", attrs: { level: 6 } }],
- toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
+ toDOM(node) {
+ const dom = toParagraphDOM(node) as any;
+ const level = node.attrs.level || 1;
+ dom[0] = 'h' + level;
+ return dom;
+ },
+ getAttrs(dom: any) {
+ const attrs = getParagraphNodeAttrs(dom) as any;
+ const level = Number(dom.nodeName.substring(1)) || 1;
+ attrs.level = level;
+ return attrs;
+ }
},
// :: NodeSpec A code listing. Disallows marks or non-text inline
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 6592c488b..7bea8d01b 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -84,11 +84,6 @@ export default class PDFMenu extends AntimodeMenu {
e.preventDefault();
}
- togglePin = action((e: React.MouseEvent) => {
- this.Pinned = !this.Pinned;
- !this.Pinned && (this.Highlighting = false);
- });
-
@action
highlightClicked = (e: React.MouseEvent) => {
if (!this.Highlight(this.highlightColor) && this.Pinned) {
@@ -98,7 +93,7 @@ export default class PDFMenu extends AntimodeMenu {
@computed get highlighter() {
const button =
- <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={this.highlightClicked}>
+ <button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onPointerDown={this.highlightClicked}>
<FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
<div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
</button>;
@@ -161,8 +156,6 @@ export default class PDFMenu extends AntimodeMenu {
this.highlighter,
<button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
<FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
- <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /></button>,
] : [
<button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
<FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5ed842ab7..cfa9a1844 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -4,16 +4,16 @@ 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 } from "../../../fields/Doc";
+import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym, AclAddonly, AclEdit, AclAdmin } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
import { List } from "../../../fields/List";
-import { createSchema, makeInterface } from "../../../fields/Schema";
-import { ScriptField } from "../../../fields/ScriptField";
+import { createSchema, makeInterface, listSpec } from "../../../fields/Schema";
+import { ScriptField, ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
import { PdfField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
+import { TraceMobx, GetEffectiveAcl } 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";
@@ -106,6 +106,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
private _scrollTopReactionDisposer?: IReactionDisposer;
private _filterReactionDisposer?: IReactionDisposer;
private _searchReactionDisposer?: IReactionDisposer;
+ private _searchReactionDisposer2?: IReactionDisposer;
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _selectionText: string = "";
@@ -336,6 +337,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
nextAnnotation = () => {
this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1);
this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
+ this.Document.searchIndex = this.Index;
+
}
@action
@@ -344,9 +347,14 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
@action
+ scrollToFrame = (duration: number, top: number) => {
+ this._mainCont.current && smoothScroll(duration, this._mainCont.current, top);
+ }
+
+ @action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
if (scrollToAnnotation) {
- const offset = this.visibleHeight() / 2 * 96 / 72;
+ const offset = this.visibleHeight() / 2;
this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
Doc.linkFollowHighlight(scrollToAnnotation);
}
@@ -403,6 +411,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
phraseSearch: true,
query: searchString
});
+ this.Document.searchIndex = this.Index;
}
else if (this._mainCont.current) {
const executeFind = () => {
@@ -416,7 +425,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
};
this._mainCont.current.addEventListener("pagesloaded", executeFind);
this._mainCont.current.addEventListener("pagerendered", executeFind);
+ this.Document.searchIndex = this.Index;
}
+
}
@action
@@ -565,9 +576,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
highlight = (color: string) => {
// creates annotation documents for current highlights
- const annotationDoc = this.makeAnnotationDocument(color);
- annotationDoc && this.props.addDocument?.(annotationDoc);
- return annotationDoc;
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color);
+ annotationDoc && this.addDocument?.(annotationDoc);
+ return annotationDoc as Doc ?? undefined;
}
/**
@@ -705,7 +717,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
marqueeX = () => this._marqueeX;
marqueeY = () => this._marqueeY;
marqueeing = () => this._marqueeing;
- visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
+ visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling();
contentZoom = () => this._zoomed;
render() {
TraceMobx();
diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss
index dbe6b0d4f..1e776384a 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/presentationview/PresElementBox.scss
@@ -1,11 +1,18 @@
+$light-blue: #AEDDF8;
+$dark-blue: #5B9FDD;
+$light-background: #ececec;
+
.presElementBox-item {
- display: inline-block;
- background-color: #eeeeee;
+ display: grid;
+ grid-template-columns: max-content max-content max-content max-content;
+ background-color: #d5dce2;
+ font-family: Roboto;
+ letter-spacing: normal;
+ position: relative;
pointer-events: all;
width: 100%;
height: 100%;
- outline-color: maroon;
- outline-style: dashed;
+ font-weight: 400;
border-radius: 6px;
-webkit-touch-callout: none;
-webkit-user-select: none;
@@ -13,10 +20,36 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all .3s;
padding: 0px;
padding-bottom: 3px;
+ .presElementBox-highlight {
+ position: absolute;
+ transform: translate(-100px, -4px);
+ z-index: -1;
+ width: calc(100% + 200px);
+ height: calc(100% + 8px);
+ background-color: $light-blue;
+ }
+
+ .presElementBox-highlightTop {
+ position: absolute;
+ transform: translate(-100px, -4px);
+ z-index: -1;
+ width: calc(100% + 200px);
+ height: calc(50% + 4px);
+ }
+
+ .presElementBox-highlightBottom {
+ position: absolute;
+ transform: translate(-100px, 0px);
+ z-index: -1;
+ top: 50%;
+ width: calc(100% + 200px);
+ height: calc(50% + 4px);
+ }
+
.documentView-node {
position: absolute;
z-index: 1;
@@ -33,62 +66,82 @@
.presElementBox-item:hover {
transition: all .1s;
- background: #AAAAAA;
+ background: #98b7da;
border-radius: 6px;
}
.presElementBox-active {
- background: gray;
color: black;
border-radius: 6px;
- box-shadow: black 2px 2px 5px;
+ border: solid 2px $dark-blue;
}
-.presElementBox-closeIcon {
- border-radius: 20px;
- transform: scale(0.7);
- position: absolute;
- right: 0;
- top: 0;
- padding: 8px;
-}
-
-
.presElementBox-buttons {
- display: flow-root;
- position: relative;
+ display: grid;
+ grid-template-rows: 15px;
+ top: 15px;
+ left: -20;
+ position: absolute;
width: 100%;
height: auto;
.presElementBox-interaction {
- color: gray;
- float: left;
- padding: 0px;
- width: 20px;
- height: 20px;
+ display: none;
}
.presElementBox-interaction-selected {
- color: white;
+ color: grey;
+ background-color: rgba(0, 0, 0, 0);
float: left;
padding: 0px;
width: 20px;
height: 20px;
- border: solid 1px darkgray;
}
}
-.presElementBox-name {
+.presElementBox-number {
font-size: 12px;
+ width: 20;
+ font-weight: 700;
+ text-align: right;
+ justify-content: center;
+ align-content: center;
+ left: -20;
position: absolute;
display: inline-block;
- width: calc(100% - 45px);
+ overflow: hidden;
+}
+
+.presElementBox-name {
+ align-self: center;
+ font-size: 13px;
+ font-family: Roboto;
+ font-weight: 500;
+ position: relative;
+ top: 1px;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
text-overflow: ellipsis;
overflow: hidden;
white-space: pre;
}
+.presElementBox-time {
+ align-self: center;
+ position: relative;
+ top: 2px;
+ padding-right: 10px;
+ font-size: 10;
+ font-weight: 300;
+ font-family: Roboto;
+ z-index: 300;
+ letter-spacing: normal;
+}
+
.presElementBox-embedded {
+ grid-column: 1/8;
position: relative;
display: flex;
width: auto;
@@ -100,51 +153,56 @@
width: 100%;
height: 100%;
position: absolute;
- left: 0;
+ border-radius: 3px;
top: 0;
- background: transparent;
- z-index: 2;
+ left: 0;
+ z-index: 1;
+ overflow: hidden;
}
-@media only screen and (max-device-width: 480px) {
- .presElementBox-buttons {
- display: inline-flex;
- position: absolute;
- top: 0;
- right: 0;
- z-index: 3;
- width: 50%;
-
- .presElementBox-interaction {
- width: 50;
- height: 50;
- }
-
- .presElementBox-interaction-selected {
- width: 50;
- height: 50;
- }
- }
-
- .presElementBox-item {
- display: inline-flex;
- overflow: hidden;
- }
-
- .presElementBox-closeIcon {
- transform: scale(1.5);
- right: 10;
- top: 10;
- }
+.presElementBox-closeIcon {
+ position: absolute;
+ border-radius: 100%;
+ z-index: 300;
+ right: 3px;
+ top: 3px;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ font-size: 75%;
+ background-color: black;
+ color: white;
+ justify-content: center;
+ align-items: center;
+}
- .presElementBox-name {
- font-size: 30px;
- top: 10px;
- z-index: 3;
- width: 50%;
- }
+.presElementBox-expand {
+ position: absolute;
+ border-radius: 100%;
+ z-index: 300;
+ right: 26px;
+ top: 3px;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ font-size: 75%;
+ background-color: black;
+ color: white;
+ justify-content: center;
+ align-items: center;
+}
- .presElementBox-embedded {
- transform: translate(0, 90px) scale(1.5);
- }
+.presElementBox-expand-selected {
+ position: absolute;
+ border-radius: 100%;
+ right: 3px;
+ bottom: 3px;
+ width: 20px;
+ height: 20px;
+ z-index: 300;
+ display: flex;
+ background-color: black;
+ color: white;
+ justify-content: center;
+ align-items: center;
} \ No newline at end of file
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 4c0972736..a6dbb76ef 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -5,8 +5,8 @@ import { Doc, DataSym, DocListCast } from "../../../fields/Doc";
import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from "../../../fields/FieldSymbols";
import { createSchema, makeInterface, listSpec } from '../../../fields/Schema';
-import { Cast, NumCast, BoolCast, ScriptCast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange } from "../../../Utils";
+import { Cast, NumCast, BoolCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange, setupMoveUpEvents } from "../../../Utils";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -15,7 +15,10 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
import React = require("react");
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { PresBox } from "../nodes/PresBox";
import { DocumentType } from "../../documents/DocumentTypes";
+import { Tooltip } from "@material-ui/core";
+import { DragManager } from "../../util/DragManager";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -38,19 +41,18 @@ const PresDocument = makeInterface(presSchema, documentSchema);
@observer
export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
-
_heightDisposer: IReactionDisposer | undefined;
// these fields are conditionally computed fields on the layout document that take this document as a parameter
@computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
- @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation elemnt template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
- @computed get presStatus() { return BoolCast(this.lookupField("presStatus")); }
+ @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
+ @computed get presStatus() { return StrCast(this.lookupField("presStatus")); }
@computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
@computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
@computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
componentDidMount() {
this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
- params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 200 : 0), { fireImmediately: true });
+ params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
}
componentWillUnmount() {
this._heightDisposer?.();
@@ -69,7 +71,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
this.targetDoc.opacity = 1;
}
} else {
- if (this.presStatus && this.indexInPres > this.itemIndex && this.targetDoc) {
+ if (this.presStatus !== "edit" && this.indexInPres > this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 0;
}
}
@@ -90,7 +92,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
}
} else {
if (this.rootDoc.presFadeButton) this.rootDoc.presFadeButton = false;
- if (this.presStatus && this.indexInPres < this.itemIndex && this.targetDoc) {
+ if (this.presStatus !== "edit" && this.indexInPres < this.itemIndex && this.targetDoc) {
this.targetDoc.opacity = 0;
}
}
@@ -125,7 +127,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
}
} else {
this.rootDoc.presHideAfterButton = false;
- if (this.presStatus && (this.indexInPres < this.itemIndex) && this.targetDoc) {
+ if (this.presStatus !== "edit" && (this.indexInPres < this.itemIndex) && this.targetDoc) {
this.targetDoc.opacity = 0.5;
}
}
@@ -166,10 +168,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
*/
ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
- embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
+ @action
+ presExpandDocumentClick = () => {
+ this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton;
+ }
+
+ embedHeight = () => 100;
+ // embedWidth = () => this.props.PanelWidth();
+ // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
embedWidth = () => this.props.PanelWidth() - 20;
/**
- * The function that is responsible for rendering the a preview or not for this
+ * The function that is responsible for rendering a preview or not for this
* presentation element.
*/
@computed get renderEmbeddedInline() {
@@ -207,33 +216,144 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
</div>;
}
+ @computed get duration() {
+ let durationInS: number;
+ if (this.targetDoc.presDuration) durationInS = NumCast(this.targetDoc.presDuration) / 1000;
+ else durationInS = 2;
+ return "D: " + durationInS + "s";
+ }
+
+ @computed get transition() {
+ let transitionInS: number;
+ if (this.targetDoc.presTransition) transitionInS = NumCast(this.targetDoc.presTransition) / 1000;
+ else transitionInS = 0.5;
+ return "M: " + transitionInS + "s";
+ }
+
+ private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+
+ headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ const element = document.elementFromPoint(e.clientX, e.clientY)?.parentElement;
+ e.stopPropagation();
+ e.preventDefault();
+ if (element) {
+ if (PresBox.Instance._eleArray.includes(element)) {
+ setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
+ }
+ }
+ }
+
+ headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ DragManager.docsBeingDragged = [];
+ this._highlightTopRef.current!.style.borderBottom = "0px";
+ this._highlightBottomRef.current!.style.borderBottom = "0px";
+ }
+
+ startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
+ const activeItem = this.rootDoc;
+ const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray().map(doc => doc));
+ const dragItem: HTMLElement[] = [];
+ PresBox.Instance._dragArray.map(ele => {
+ const drag = ele;
+ drag.style.backgroundColor = "#d5dce2";
+ drag.style.borderRadius = '5px';
+ dragItem.push(drag);
+ });
+ if (activeItem) {
+ DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY);
+ activeItem.dragging = true;
+ return true;
+ }
+ return false;
+ }
+
+ private _highlightTopRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _highlightBottomRef: React.RefObject<HTMLDivElement> = React.createRef();
+
+
+ onPointerTop = (e: React.PointerEvent<HTMLDivElement>) => {
+ if (DragManager.docsBeingDragged.length > 0) {
+ this._highlightTopRef.current!.style.borderTop = "solid 2px #5B9FDD";
+ }
+ }
+
+ onPointerBottom = (e: React.PointerEvent<HTMLDivElement>) => {
+ if (DragManager.docsBeingDragged.length > 0) {
+ this._highlightBottomRef.current!.style.borderBottom = "solid 2px #5B9FDD";
+ }
+ }
+
+ onPointerLeave = (e: React.PointerEvent<HTMLDivElement>) => {
+ if (DragManager.docsBeingDragged.length > 0) {
+ this._highlightBottomRef.current!.style.borderBottom = "0px";
+ this._highlightTopRef.current!.style.borderTop = "0px";
+ }
+ }
+
render() {
const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree;
- const className = "presElementBox-item" + (this.itemIndex === this.indexInPres ? " presElementBox-active" : "");
+ const className = "presElementBox-item" + (PresBox.Instance._selectedArray.includes(this.rootDoc) ? " presElementBox-active" : "");
const pbi = "presElementBox-interaction";
return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : (
<div className={className} key={this.props.Document[Id] + this.indexInPres}
+ ref={this._itemRef}
style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }}
- onClick={e => { this.props.focus(this.rootDoc); e.stopPropagation(); }}>
- {treecontainer ? (null) : <>
- <strong className="presElementBox-name">
- {`${this.indexInPres + 1}. ${this.targetDoc?.title}`}
- </strong>
- <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => {
- this.props.removeDocument?.(this.rootDoc);
- e.stopPropagation();
- }}>X</button>
- <br />
- </>}
- <div className="presElementBox-buttons">
+ onClick={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ // Command/ control click
+ if (e.ctrlKey || e.metaKey) {
+ PresBox.Instance.multiSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!);
+ // Shift click
+ } else if (e.shiftKey) {
+ PresBox.Instance.shiftSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!);
+ // Regular click
+ } else {
+ this.props.focus(this.rootDoc);
+ PresBox.Instance._eleArray = [];
+ PresBox.Instance._eleArray.push(this._itemRef.current!);
+ PresBox.Instance._dragArray = [];
+ PresBox.Instance._dragArray.push(this._dragRef.current!);
+ }
+ }}
+ onPointerDown={this.headerDown}
+ onPointerUp={this.headerUp}
+ >
+ <>
+ <div className="presElementBox-number">
+ {`${this.indexInPres + 1}.`}
+ </div>
+ <div ref={this._dragRef} className="presElementBox-name" style={{ maxWidth: (PresBox.Instance.toolbarWidth - 70) }}>
+ {`${this.targetDoc?.title}`}
+ </div>
+ <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.transition}</div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.duration}</div></Tooltip>
+ <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();
+ }}>
+ <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(); }}>
+ <FontAwesomeIcon icon={(this.rootDoc.presExpandInlineButton ? "angle-up" : "angle-down")} onPointerDown={e => e.stopPropagation()} />
+ </div></Tooltip>
+ </>
+ <div ref={this._highlightTopRef} onPointerOver={this.onPointerTop} onPointerLeave={this.onPointerLeave} className="presElementBox-highlightTop" style={{ zIndex: 299, backgroundColor: "rgba(0,0,0,0)" }} />
+ <div ref={this._highlightBottomRef} onPointerOver={this.onPointerBottom} onPointerLeave={this.onPointerLeave} className="presElementBox-highlightBottom" style={{ zIndex: 299, backgroundColor: "rgba(0,0,0,0)" }} />
+ <div className="presElementBox-highlight" style={{ backgroundColor: PresBox.Instance._selectedArray.includes(this.rootDoc) ? "#AEDDF8" : "rgba(0,0,0,0)" }} />
+ <div className="presElementBox-buttons" style={{ display: this.rootDoc.presExpandInlineButton ? "grid" : "none" }}>
<button title="Zoom" className={pbi + (this.rootDoc.presZoomButton ? "-selected" : "")} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Navigate" className={pbi + (this.rootDoc.presNavButton ? "-selected" : "")} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Hide Before" className={pbi + (this.rootDoc.presHideTillShownButton ? "-selected" : "")} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={"file"} onPointerDown={e => e.stopPropagation()} /></button>
- <button title="Fade After" className={pbi + (this.rootDoc.presFadeButton ? "-selected" : "")} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Hide After" className={pbi + (this.rootDoc.presHideAfterButton ? "-selected" : "")} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
- <button title="Group With Up" className={pbi + (this.rootDoc.presGroupButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presGroupButton = !this.rootDoc.presGroupButton; }}><FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /></button>
- <button title="Progressivize" className={pbi + (this.rootDoc.pres ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button>
- <button title="Expand Inline" className={pbi + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Progressivize" className={pbi + (this.rootDoc.presProgressivize ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Effect" className={pbi + (this.rootDoc.presEffect ? "-selected" : "")}>E</button>
</div>
{this.renderEmbeddedInline}
</div>
diff --git a/src/client/views/search/FieldFilters.scss b/src/client/views/search/FieldFilters.scss
deleted file mode 100644
index e1d0d8df5..000000000
--- a/src/client/views/search/FieldFilters.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.field-filters {
- width: 100%;
- display: grid;
- // grid-template-columns: 18% 20% 60%;
- grid-template-columns: 20% 25% 60%;
-}
-
-.field-filters-required {
- width: 100%;
- display: grid;
- grid-template-columns: 50% 50%;
-} \ No newline at end of file
diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx
deleted file mode 100644
index 7a33282d2..000000000
--- a/src/client/views/search/FieldFilters.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import * as React from 'react';
-import { observable } from 'mobx';
-import { CheckBox } from './CheckBox';
-import { Keys } from './FilterBox';
-import "./FieldFilters.scss";
-
-export interface FieldFilterProps {
- titleFieldStatus: boolean;
- dataFieldStatus: boolean;
- authorFieldStatus: boolean;
- updateTitleStatus(stat: boolean): void;
- updateAuthorStatus(stat: boolean): void;
- updateDataStatus(stat: boolean): void;
-}
-
-export class FieldFilters extends React.Component<FieldFilterProps> {
-
- static Instance: FieldFilters;
-
- @observable public _resetBoolean = false;
- @observable public _resetCounter: number = 0;
-
- constructor(props: FieldFilterProps) {
- super(props);
- FieldFilters.Instance = this;
- }
-
- resetFieldFilters() {
- this._resetBoolean = true;
- }
-
- render() {
- return (
- <div className="field-filters">
- <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} />
- <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} />
- <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} />
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss
deleted file mode 100644
index 094ea9cc5..000000000
--- a/src/client/views/search/FilterBox.scss
+++ /dev/null
@@ -1,178 +0,0 @@
-@import "../globalCssVariables";
-@import "./NaviconButton.scss";
-
-.filter-form {
- padding: 25px;
- width: 440px;
- position: relative;
- right: 1px;
- color: grey;
- flex-direction: column;
- display: inline-block;
- transform-origin: top;
- overflow: auto;
- border-bottom: solid black 3px;
-
- .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/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
deleted file mode 100644
index eb61f9a14..000000000
--- a/src/client/views/search/FilterBox.tsx
+++ /dev/null
@@ -1,431 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import "./SearchBox.scss";
-import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { Doc } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
-import { DocumentType } from "../../documents/DocumentTypes";
-import { Cast, StrCast } from '../../../fields/Types';
-import * as _ from "lodash";
-import { IconBar } from './IconBar';
-import { FieldFilters } from './FieldFilters';
-import { SelectionManager } from '../../util/SelectionManager';
-import { DocumentView } from '../nodes/DocumentView';
-import { CollectionFilters } from './CollectionFilters';
-import * as $ from 'jquery';
-import "./FilterBox.scss";
-import { SearchBox } from './SearchBox';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-library.add(faTimes);
-library.add(faCheckCircle);
-library.add(faObjectGroup);
-
-export enum Keys {
- TITLE = "title",
- AUTHOR = "author",
- DATA = "data"
-}
-
-@observer
-export class FilterBox extends React.Component {
-
- static Instance: FilterBox;
- public _allIcons: string[] = [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 _basicWordStatus: boolean = true;
- @observable private _filterOpen: boolean = false;
- //if icons = all icons, then no icon filter is applied
- @observable private _icons: string[] = this._allIcons;
- //if all of these are true, no key filter is applied
- @observable private _anyKeywordStatus: boolean = true;
- @observable private _allKeywordStatus: boolean = true;
- @observable private _titleFieldStatus: boolean = true;
- @observable private _authorFieldStatus: boolean = true;
- @observable private _dataFieldStatus: boolean = true;
- //this also serves as an indicator if the collection status filter is applied
- @observable public _deletedDocsStatus: boolean = false;
- @observable private _collectionStatus = false;
- @observable private _collectionSelfStatus = true;
- @observable private _collectionParentStatus = true;
- @observable private _wordStatusOpen: boolean = false;
- @observable private _typeOpen: boolean = false;
- @observable private _colOpen: boolean = false;
- @observable private _fieldOpen: boolean = false;
- public _pointerTime: number = -1;
-
- constructor(props: Readonly<{}>) {
- super(props);
- FilterBox.Instance = this;
- }
- setupAccordion() {
- $('document').ready(function () {
- const acc = document.getElementsByClassName('filter-header');
- // tslint:disable-next-line: prefer-for-of
- for (let i = 0; i < acc.length; i++) {
- acc[i].addEventListener("click", function (this: HTMLElement) {
- this.classList.toggle("active");
-
- const panel = this.nextElementSibling as HTMLElement;
- if (panel.style.maxHeight) {
- panel.style.overflow = "hidden";
- panel.style.maxHeight = "";
- panel.style.opacity = "0";
- } else {
- setTimeout(() => {
- panel.style.overflow = "visible";
- }, 200);
- setTimeout(() => {
- panel.style.opacity = "1";
- }, 50);
- panel.style.maxHeight = panel.scrollHeight + "px";
-
- }
- });
-
- const el = acc[i] as HTMLElement;
- el.click();
- }
- });
- }
-
- @action.bound
- minimizeAll() {
- $('document').ready(function () {
- const acc = document.getElementsByClassName('filter-header');
-
- // tslint:disable-next-line: prefer-for-of
- for (var i = 0; i < acc.length; i++) {
- const classList = acc[i].classList;
- if (classList.contains("active")) {
- acc[i].classList.toggle("active");
- const panel = acc[i].nextElementSibling as HTMLElement;
- panel.style.overflow = "hidden";
- panel.style.maxHeight = "";
- }
- }
- });
- }
-
- @action.bound
- resetFilters = () => {
- this._basicWordStatus = true;
- IconBar.Instance.selectAll();
- FieldFilters.Instance.resetFieldFilters();
- }
-
- basicRequireWords(query: string): string {
- const oldWords = query.split(" ");
- const newWords: string[] = [];
- oldWords.forEach(word => {
- const newWrd = "+" + word;
- newWords.push(newWrd);
- });
- query = newWords.join(" ");
-
- return query;
- }
-
- basicFieldFilters(query: string, type: string): string {
- const oldWords = query.split(" ");
- let mod = "";
-
- if (type === Keys.AUTHOR) {
- mod = " author_t:";
- } if (type === Keys.DATA) {
- //TODO
- } if (type === Keys.TITLE) {
- mod = " title_t:";
- }
-
- const newWords: string[] = [];
- oldWords.forEach(word => {
- const newWrd = mod + word;
- newWords.push(newWrd);
- });
-
- query = newWords.join(" ");
-
- return query;
- }
-
- applyBasicFieldFilters(query: string) {
- let finalQuery = "";
-
- if (this._titleFieldStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE);
- }
- if (this._authorFieldStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
- }
- if (this._deletedDocsStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
- }
- return finalQuery;
- }
-
- get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
-
- //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;
- }
-
- getFinalQuery(query: string): string {
- //alters the query so it looks in the correct fields
- //if this is true, then not all of the field boxes are checked
- //TODO: data
- if (this.fieldFiltersApplied) {
- query = this.applyBasicFieldFilters(query);
- query = query.replace(/\s+/g, ' ').trim();
- }
-
- //alters the query based on if all words or any words are required
- //if this._wordstatus is false, all words are required and a + is added before each
- if (!this._basicWordStatus) {
- query = this.basicRequireWords(query);
- query = query.replace(/\s+/g, ' ').trim();
- }
-
- //if should be searched in a specific collection
- if (this._collectionStatus) {
- query = this.addCollectionFilter(query);
- query = query.replace(/\s+/g, ' ').trim();
- }
- return query;
- }
-
- 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 === 9 ? undefined : this._icons;
- }
-
- @action
- filterDocsByType(docs: Doc[]) {
- if (this._icons.length === 9) {
- return docs;
- }
- const finalDocs: Doc[] = [];
- docs.forEach(doc => {
- const layoutresult = Cast(doc.type, "string");
- if (layoutresult && this._icons.includes(layoutresult)) {
- finalDocs.push(doc);
- }
- });
- return finalDocs;
- }
-
- getABCicon() {
- return (
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35">
- <path d="M25.4 47.9c-1.3 1.3-1.9 2.8-1.9 4.8 0 3.8 2.3 6.1 6.1 6.1 5.1 0 8-3.3 9-6.2 0.2-0.7 0.4-1.4 0.4-2.1v-6.1c-0.1 0-0.1 0-0.2 0C32.2 44.5 27.7 45.6 25.4 47.9z" />
- <path d="M64.5 28.6c-2.2 0-4.1 1.5-4.7 3.8l0 0.2c-0.1 0.3-0.1 0.7-0.1 1.1v3.3c0 0.4 0.1 0.8 0.2 1.1 0.6 2.2 2.4 3.6 4.6 3.6 3.2 0 5.2-2.6 5.2-6.7C69.5 31.8 68 28.6 64.5 28.6z" />
- <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM40.1 65.5l-0.5-4c-3 3.1-7.4 4.9-12.1 4.9 -6.8 0-13.6-4.4-13.6-12.8 0-4 1.3-7.4 4-10 4.1-4.1 11.1-6.2 20.8-6.3 0-5.5-2.9-8.4-8.3-8.4 -3.6 0-7.4 1.1-10.2 2.9l-1.1 0.7 -2.4-6.9 0.7-0.4c3.7-2.4 8.9-3.8 14.1-3.8 10.9 0 16.7 6.2 16.7 17.9V54.6c0 4.1 0.2 7.2 0.7 9.7L49 65.5H40.1zM65.5 67.5c1.8 0 3-0.5 4-0.9l0.5-0.2 0.8 3.4 -0.3 0.2c-1 0.5-3 1.1-5.5 1.1 -5.8 0-9.7-4-9.7-9.9 0-6.1 4.3-10.3 10.4-10.3 2.1 0 4 0.5 4.9 1l0.3 0.2 -1 3.5 -0.5-0.3c-0.7-0.4-1.8-0.8-3.7-0.8 -3.7 0-6.1 2.6-6.1 6.6C59.5 64.8 61.9 67.5 65.5 67.5zM65 45.3c-2.5 0-4.5-0.9-5.9-2.7l-0.1 2.3h-3.8l0-0.5c0.1-1.2 0.2-3.1 0.2-4.8V16.7h4.3v10.8c1.4-1.6 3.5-2.5 6-2.5 2.2 0 4.1 0.8 5.5 2.3 1.8 1.8 2.8 4.5 2.8 7.7C73.8 42.1 69.3 45.3 65 45.3z" />
- </svg>
- );
- }
-
- getTypeIcon() {
- return (
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35">
- <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM43.9 12.2c4.1 0 7.5 3.4 7.5 7.5 0 4.1-3.4 7.5-7.5 7.5 -4.1 0-7.5-3.4-7.5-7.5C36.4 15.5 39.7 12.2 43.9 12.2zM11.9 50.4l7.5-13 7.5 13H11.9zM47.6 75.7h-7.5l-3.7-6.5 3.8-6.5h7.5l3.8 6.5L47.6 75.7zM70.7 70.7c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3l-25.4-25.4 -25.4 25.4c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1 0-1.4l25.4-25.4 -25.4-25.4c-0.4-0.4-0.4-1 0-1.4s1-0.4 1.4 0l25.4 25.4 25.4-25.4c0.4-0.4 1-0.4 1.4 0s0.4 1 0 1.4l-25.4 25.4 25.4 25.4C71.1 69.7 71.1 70.3 70.7 70.7zM61.4 51.4v-15h15v15H61.4z" />
- </svg>
- );
- }
-
- getKeyIcon() {
- return (
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35">
- <path d="M38.5 32.4c0 3.4-2.7 6.1-6.1 6.1 -3.4 0-6.1-2.7-6.1-6.1 0-3.4 2.8-6.1 6.1-6.1C35.8 26.3 38.5 29 38.5 32.4zM87.8 43.9c0 24.2-19.6 43.9-43.9 43.9S0 68.1 0 43.9C0 19.7 19.7 0 43.9 0S87.8 19.7 87.8 43.9zM66.8 60.3L50.2 43.7c-0.5-0.5-0.6-1.2-0.4-1.8 2.4-5.6 1.1-12.1-3.2-16.5 -5.9-5.8-15.4-5.8-21.2 0l0 0c-4.3 4.3-5.6 10.8-3.2 16.5 3.2 7.6 12 11.2 19.7 8 0.6-0.3 1.4-0.1 1.8 0.4l3.1 3.1h3.9c1.2 0 2.2 1 2.2 2.2v3.6h3.6c1.2 0 2.2 1 2.2 2.2v4l1.6 1.6h6.5V60.3z" />
- </svg>
- );
- }
-
- getColIcon() {
- return (
- <div className="col-icon">
- <FontAwesomeIcon icon={faObjectGroup} size="lg" />
- </div>
- );
- }
-
- @action.bound
- openFilter = () => {
- this._filterOpen = !this._filterOpen;
- SearchBox.Instance.closeResults();
- this.setupAccordion();
- }
-
- //if true, any keywords can be used. if false, all keywords are required.
- @action.bound
- handleWordQueryChange = () => {
- this._basicWordStatus = !this._basicWordStatus;
- }
-
- @action.bound
- updateIcon(newArray: string[]) { this._icons = newArray; }
-
- @action.bound
- getIcons(): string[] { return this._icons; }
-
- stopProp = (e: React.PointerEvent) => {
- e.stopPropagation();
- this._pointerTime = e.timeStamp;
- }
-
- @action.bound
- public closeFilter() {
- this._filterOpen = false;
- }
-
- @action.bound
- updateAnyKeywordStatus(newStat: boolean) { this._anyKeywordStatus = newStat; }
-
- @action.bound
- updateAllKeywordStatus(newStat: boolean) { this._allKeywordStatus = newStat; }
-
- @action.bound
- updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; }
-
- @action.bound
- updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; }
-
- @action.bound
- updateDataStatus(newStat: boolean) { this._deletedDocsStatus = newStat; }
-
- @action.bound
- updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; }
-
- @action.bound
- updateSelfCollectionStatus(newStat: boolean) { this._collectionSelfStatus = newStat; }
-
- @action.bound
- updateParentCollectionStatus(newStat: boolean) { this._collectionParentStatus = newStat; }
-
- getAnyKeywordStatus() { return this._anyKeywordStatus; }
- getAllKeywordStatus() { return this._allKeywordStatus; }
- getCollectionStatus() { return this._collectionStatus; }
- getSelfCollectionStatus() { return this._collectionSelfStatus; }
- getParentCollectionStatus() { return this._collectionParentStatus; }
- getTitleStatus() { return this._titleFieldStatus; }
- getAuthorStatus() { return this._authorFieldStatus; }
- getDataStatus() { return this._deletedDocsStatus; }
-
- getActiveFilters() {
- return (
- <div className="active-filters">
- {!this._basicWordStatus ? <div className="active-icon container">
- <div className="active-icon icon">{this.getABCicon()}</div>
- <div className="active-icon description">Required Words Applied</div>
- </div> : undefined}
- {!(this._icons.length === 9) ? <div className="active-icon container">
- <div className="active-icon icon">{this.getTypeIcon()}</div>
- <div className="active-icon description">Type Filters Applied</div>
- </div> : undefined}
- {!(this._authorFieldStatus && this._dataFieldStatus && this._titleFieldStatus) ?
- <div className="active-icon container">
- <div className="active-icon icon">{this.getKeyIcon()}</div>
- <div className="active-icon description">Field Filters Applied</div>
- </div> : undefined}
- {this._collectionStatus ? <div className="active-icon container">
- <div className="active-icon icon">{this.getColIcon()}</div>
- <div className="active-icon description">Collection Filters Active</div>
- </div> : undefined}
- </div>
- );
- }
-
- // Useful queries:
- // Delegates of a document: {!join from=id to=proto_i}id:{protoId}
- // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId} //id of collections prototype
- render() {
- return (
- <div>
- <div style={{ display: "flex", flexDirection: "row-reverse" }}>
- <SearchBox />
- {/* {this.getActiveFilters()} */}
- </div>
- {this._filterOpen ? (
- <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex", background: "black" } : { display: "none" }}>
- <div className="top-filter-header" style={{ display: "flex", width: "100%" }}>
- <div id="header">Filter Search Results</div>
- <div style={{ marginLeft: "auto" }}></div>
- <div className="close-icon" onClick={this.closeFilter}>
- <span className="line line-1"></span>
- <span className="line line-2"></span></div>
- </div>
- <div className="filter-options">
- <div className="filter-div">
- <div className="filter-header">
- <div className='filter-title words'>Required words</div>
- </div>
- <div className="filter-panel" >
- <button className="all-filter" onClick={this.handleWordQueryChange}>Include All Keywords</button>
- </div>
- </div>
- <div className="filter-div">
- <div className="filter-header">
- <div className="filter-title icon">Filter by type of node</div>
- </div>
- <div className="filter-panel"><IconBar /></div>
- </div>
- <div className="filter-div">
- <div className="filter-header">
- <div className="filter-title field">Filter by Basic Keys</div>
- </div>
- <div className="filter-panel"><FieldFilters
- titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus}
- updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div>
- </div>
- </div>
- <div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}>
- <button className="save-filter" >Save Filters</button>
- <button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button>
- </div>
- </div>
- ) :
- undefined}
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index bb62113a1..3f06ba7d3 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -17,10 +17,9 @@
.searchBox-bar {
height: 32px;
display: flex;
- justify-content: flex-end;
+ justify-content: center;
align-items: center;
- padding-left: 2px;
-
+ background-color: black;
.searchBox-barChild {
&.searchBox-collection {
@@ -30,24 +29,29 @@
}
&.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-input:focus {
width: 500px;
- outline: 3px solid lightblue;
+ outline:none;
}
&.searchBox-filter {
align-self: stretch;
+ button{
+ transform:none;
+ }
button:hover{
- transform:scale(1.0);
- background:"#121721";
+ transform:none;
}
}
@@ -81,8 +85,6 @@
.no-result {
width: 500px;
background: $light-color-secondary;
- border-color: $intermediate-color;
- border-bottom-style: solid;
padding: 10px;
height: 50px;
text-transform: uppercase;
@@ -96,20 +98,20 @@
background: #121721;
flex-direction: column;
transform-origin: top;
- transition: height 0.3s ease, display 0.6s ease;
+ transition: height 0.3s ease, display 0.6s ease, overflow 0.6s ease;
height:0px;
overflow:hidden;
.filter-header {
- display: flex;
+ //display: flex;
position: relative;
- flex-wrap:wrap;
+ //flex-wrap:wrap;
right: 1px;
color: grey;
- flex-direction: row-reverse;
+ //flex-direction: row-reverse;
transform-origin: top;
- justify-content: space-evenly;
+ //justify-content: space-evenly;
margin-bottom: 5px;
overflow:hidden;
transition:height 0.3s ease-out;
@@ -130,9 +132,7 @@
color: grey;
transform-origin: top;
border-top: 0px;
- //padding-top: 5px;
- margin-left: 10px;
- margin-right: 10px;
+
overflow:hidden;
transition:height 0.3s ease-out;
height:0px;
@@ -144,30 +144,25 @@
color: grey;
transform-origin: top;
border-top: 0px;
- //padding-top: 5px;
- margin-left: 10px;
- margin-right: 10px;
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-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;
-
- }
- }
-
-
+ // .filter-item {
+ // position: relative;
+ // border:1px solid grey;
+ // border-radius: 16px;
+ // }
+ // }
}
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 99fa6da21..1e44a379b 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,59 +1,66 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, IReactionDisposer, reaction } from 'mobx';
+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 * as React from 'react';
import * as rp from 'request-promise';
-import { Doc } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { documentSchema } from "../../../fields/documentSchemas";
import { 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 { Utils } from '../../../Utils';
+import { returnFalse, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { SetupDrag } from '../../util/DragManager';
import { SearchUtil } from '../../util/SearchUtil';
-import "./SearchBox.scss";
-import { SearchItem } from './SearchItem';
-import { IconBar } from './IconBar';
-import { FieldView } from '../nodes/FieldView';
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentView } from '../nodes/DocumentView';
import { SelectionManager } from '../../util/SelectionManager';
-import { listSpec } from '../../../fields/Schema';
-
-library.add(faTimes);
+import { Transform } from '../../util/Transform';
+import { CollectionView, CollectionViewType } from '../collections/CollectionView';
+import { ViewBoxBaseComponent } from "../DocComponent";
+import { DocumentView } from '../nodes/DocumentView';
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import "./SearchBox.scss";
-export interface SearchProps {
- id: string;
- searchQuery: string;
- filterQquery?: string;
- setSearchQuery: (q: string) => {};
- searchFileTypes: string[];
- setSearchFileTypes: (types: string[]) => {};
-}
+export const searchSchema = createSchema({
+ id: "string",
+ Document: Doc,
+ searchQuery: "string",
+});
export enum Keys {
TITLE = "title",
AUTHOR = "author",
- DATA = "data"
+ DATA = "data",
+ TEXT = "text"
}
+type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
+const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
+
+//React.Component<SearchProps>
@observer
-export class SearchBox extends React.Component<SearchProps> {
+export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
- private get _searchString() { return this.props.searchQuery; }
- private set _searchString(value) { this.props.setSearchQuery(value); }
+ get _searchString() { return this.layoutDoc.searchQuery; }
+ @computed set _searchString(value) { this.layoutDoc.searchQuery = (value); }
@observable private _resultsOpen: boolean = false;
- @observable private _searchbarOpen: 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[] = [];
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 _numTotalResults = -1;
private _endIndex = -1;
@@ -63,55 +70,104 @@ export class SearchBox extends React.Component<SearchProps> {
private _curRequest?: Promise<any> = undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
-
+ 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;
+ @observable private newAssign: boolean = true;
constructor(props: any) {
super(props);
SearchBox.Instance = this;
this.resultsScrolled = this.resultsScrolled.bind(this);
+
}
+ @observable setupButtons = false;
+ componentDidMount = () => {
+ if (this.setupButtons === false) {
- componentDidMount = action(() => {
+ runInAction(() => this.setupButtons = true);
+ }
if (this.inputRef.current) {
this.inputRef.current.focus();
- this._searchbarOpen = true;
+ runInAction(() => { this._searchbarOpen = true; });
}
- if (this.props.searchQuery) { // bcz: why was this here? } && this.props.filterQquery) {
- this._searchString = this.props.searchQuery;
- this.submitSearch();
+ 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();
+ });
}
- });
+ }
@action
getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc)
+
+ @observable newsearchstring: string = "";
@action.bound
onChange(e: React.ChangeEvent<HTMLInputElement>) {
- this._searchString = e.target.value;
+ this.layoutDoc._searchString = e.target.value;
+ this.newsearchstring = e.target.value;
- this._openNoResults = false;
- this._results = [];
- this._resultsSet.clear();
- this._visibleElements = [];
- this._numTotalResults = -1;
- this._endIndex = -1;
- this._curRequest = undefined;
- this._maxSearchIndex = 0;
+
+ if (e.target.value === "") {
+ this._results.forEach(result => {
+ Doc.UnBrushDoc(result[0]);
+ result[0].searchMatch = undefined;
+ });
+
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]);
+ 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._results = [];
+ this._resultsSet.clear();
+ this._visibleElements = [];
+ this._numTotalResults = -1;
+ this._endIndex = -1;
+ this._curRequest = undefined;
+ this._maxSearchIndex = 0;
+ }
}
enter = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
+ this.layoutDoc._searchString = this.newsearchstring;
+
+ if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) {
+ runInAction(() => this.open = true);
+ }
+ else {
+ runInAction(() => this.open = false);
+
+ }
this.submitSearch();
}
}
+ @observable open: boolean = false;
+
+
public static async convertDataUri(imageUri: string, returnedFilename: string) {
try {
const posting = Utils.prepend("/uploadURI");
@@ -134,10 +190,11 @@ export class SearchBox extends React.Component<SearchProps> {
//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);
- }
+ // 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;
@@ -162,10 +219,11 @@ export class SearchBox extends React.Component<SearchProps> {
query = query.replace(/\s+/g, ' ').trim();
}
- //if should be searched in a specific collection
+ // if should be searched in a specific collection
if (this._collectionStatus) {
query = this.addCollectionFilter(query);
query = query.replace(/\s+/g, ' ').trim();
+
}
return query;
}
@@ -176,14 +234,14 @@ export class SearchBox extends React.Component<SearchProps> {
@action
filterDocsByType(docs: Doc[]) {
- if (this._icons.length === this._allIcons.length) {
- return docs;
- }
const finalDocs: Doc[] = [];
+ const blockedTypes: string[] = ["preselement", "docholder", "collection", "search", "searchitem", "script", "fonticonbox", "button", "label"];
docs.forEach(doc => {
const layoutresult = Cast(doc.type, "string");
- if (layoutresult && this._icons.includes(layoutresult)) {
- finalDocs.push(doc);
+ if (layoutresult && !blockedTypes.includes(layoutresult)) {
+ if (layoutresult && this._icons.includes(layoutresult)) {
+ finalDocs.push(doc);
+ }
}
});
return finalDocs;
@@ -216,7 +274,6 @@ export class SearchBox extends React.Component<SearchProps> {
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
@@ -236,6 +293,85 @@ export class SearchBox extends React.Component<SearchProps> {
}
+ currentSelectedCollection: DocumentView | undefined = undefined;
+ docsforfilter: Doc[] = [];
+
+ searchCollection(query: string) {
+ const selectedCollection: DocumentView = 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)]);
+ const found: [Doc, string[], string[]][] = [];
+ const docsforFilter: Doc[] = [];
+ let newarray: Doc[] = [];
+
+ while (docs.length > 0) {
+ newarray = [];
+ docs.forEach((d) => {
+ 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._numTotalResults = found.length;
+ }
+ else {
+ this.noresults = "No collection selected :(";
+ }
+
+ }
+
+
+ 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
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // 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));
+ return Array.from(Object.keys(keys));
+ }
+
applyBasicFieldFilters(query: string) {
let finalQuery = "";
@@ -248,26 +384,24 @@ export class SearchBox extends React.Component<SearchProps> {
if (this._deletedDocsStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
}
+ if (this._deletedDocsStatus) {
+ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT);
+ }
return finalQuery;
}
basicFieldFilters(query: string, type: string): string {
- const oldWords = query.split(" ");
let mod = "";
-
- if (type === Keys.AUTHOR) {
- mod = " author_t:";
- } if (type === Keys.DATA) {
- //TODO
- } if (type === Keys.TITLE) {
- mod = " title_t:";
+ switch (type) {
+ case Keys.AUTHOR: mod = " author_t:"; break;
+ case Keys.DATA: break; // TODO
+ case Keys.TITLE: mod = " _title_t:"; break;
+ case Keys.TEXT: mod = " text_t:"; break;
}
const newWords: string[] = [];
- oldWords.forEach(word => {
- const newWrd = mod + word;
- newWords.push(newWrd);
- });
+ const oldWords = query.split(" ");
+ oldWords.forEach(word => newWords.push(mod + word));
query = newWords.join(" ");
@@ -276,30 +410,64 @@ export class SearchBox extends React.Component<SearchProps> {
get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
-
@action
- submitSearch = async () => {
- const query = this._searchString;
+ submitSearch = async (reset?: boolean) => {
+ if (reset) {
+ this.layoutDoc._searchString = "";
+ }
+ this.props.Document._docFilters = undefined;
+ this.noresults = "";
+
+ this.dataDoc[this.fieldKey] = new List<Doc>([]);
+ this.headercount = 0;
+ this.children = 0;
+ this.buckets = [];
+ this.new_buckets = {};
+ const query = StrCast(this.layoutDoc._searchString);
+ Doc.SetSearchQuery(query);
this.getFinalQuery(query);
+ this._results.forEach(result => {
+ Doc.UnBrushDoc(result[0]);
+ result[0].searchMatch = undefined;
+ });
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 !== "") {
this._endIndex = 12;
this._maxSearchIndex = 0;
this._numTotalResults = -1;
- await this.getResults(query);
-
+ this.searchFullDB ? await this.getResults(query) : this.searchCollection(query);
runInAction(() => {
this._resultsOpen = true;
this._searchbarOpen = true;
this._openNoResults = 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 });
}
@@ -309,7 +477,7 @@ export class SearchBox extends React.Component<SearchProps> {
const baseExpr = "NOT baseProto_b:true";
const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true";
const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox";
- // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // this line was causing issues for me, check solr logging -syip2
+ // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`;
// fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello
const query = [baseExpr, includeDeleted, includeIcons].join(" AND ").replace(/AND $/, "");
return query;
@@ -317,21 +485,20 @@ export class SearchBox extends React.Component<SearchProps> {
getDataStatus() { return this._deletedDocsStatus; }
-
private NumResults = 25;
private lockPromise?: Promise<void>;
getResults = async (query: string) => {
+ console.log("Get");
if (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, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => {
// happens at the beginning
if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
this._numTotalResults = res.numFound;
}
-
const highlighting = res.highlighting || {};
const highlightList = res.docs.map(doc => highlighting[doc[Id]]);
const lines = new Map<string, string[]>();
@@ -340,19 +507,33 @@ export class SearchBox extends React.Component<SearchProps> {
const highlights: typeof res.highlighting = {};
docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]);
const filteredDocs = this.filterDocsByType(docs);
+
runInAction(() => {
- //this._results.push(...filteredDocs);
- filteredDocs.forEach(doc => {
+ 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)) : [];
- 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);
+ doc ? console.log(Cast(doc.context, Doc)) : null;
+ if (this.findCommonElements(hlights)) {
+ }
+ else {
+ const layoutresult = Cast(doc.type, "string");
+ if (layoutresult) {
+ if (this.new_buckets[layoutresult] === undefined) {
+ this.new_buckets[layoutresult] = 1;
+ }
+ else {
+ this.new_buckets[layoutresult] = this.new_buckets[layoutresult] + 1;
+ }
+ }
+ 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);
+ }
}
});
});
@@ -363,15 +544,16 @@ export class SearchBox extends React.Component<SearchProps> {
await this._curRequest;
}
+
this.resultsScrolled();
res();
});
return this.lockPromise;
}
-
+ @observable noresults = "";
collectionRef = React.createRef<HTMLSpanElement>();
startDragCollection = async () => {
- const res = await this.getAllResults(this.getFinalQuery(this._searchString));
+ 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);
@@ -404,7 +586,7 @@ export class SearchBox extends React.Component<SearchProps> {
y += 300;
}
}
- return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString });
+ return Docs.Create.SearchDocument({ _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString), searchQuery: StrCast(this.layoutDoc._searchString) });
}
@action.bound
@@ -417,7 +599,7 @@ export class SearchBox extends React.Component<SearchProps> {
@action.bound
closeSearch = () => {
- this.closeResults();
+ //this.closeResults();
this._searchbarOpen = false;
}
@@ -427,23 +609,30 @@ export class SearchBox extends React.Component<SearchProps> {
this._results = [];
this._resultsSet.clear();
this._visibleElements = [];
+ this._visibleDocuments = [];
this._numTotalResults = -1;
this._endIndex = -1;
this._curRequest = undefined;
}
+ @observable children: number = 0;
@action
resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
if (!this._resultsRef.current) return;
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]);
+
const scrollY = e ? e.currentTarget.scrollTop : this._resultsRef.current ? this._resultsRef.current.scrollTop : 0;
const itemHght = 53;
const startIndex = Math.floor(Math.max(0, scrollY / itemHght));
- const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght)));
-
- this._endIndex = endIndex === -1 ? 12 : endIndex;
-
+ //const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght)));
+ const endIndex = 30;
+ //this._endIndex = endIndex === -1 ? 12 : endIndex;
+ this._endIndex = 30;
+ const headers = new Set<string>(["title", "author", "lastModified", "text"]);
if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) {
- this._visibleElements = [<div className="no-result">No Search Results</div>];
+ if (this.noresults === "") {
+ this.noresults = "No search results :(";
+ }
return;
}
@@ -456,16 +645,19 @@ export class SearchBox extends React.Component<SearchProps> {
else if (this._visibleElements.length !== this._numTotalResults) {
// undefined until a searchitem is put in there
this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
- // indicates if things are placeholders
+ 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);
+ }
for (let i = 0; i < this._numTotalResults; 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 (i < startIndex || i > endIndex) {
if (this._isSearch[i] !== "placeholder") {
this._isSearch[i] = "placeholder";
+ this._isSorted[i] = "placeholder";
this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>;
}
}
@@ -473,30 +665,61 @@ export class SearchBox extends React.Component<SearchProps> {
if (this._isSearch[i] !== "search") {
let result: [Doc, string[], string[]] | undefined = undefined;
if (i >= this._results.length) {
- this.getResults(this._searchString);
+ this.getResults(StrCast(this.layoutDoc._searchString));
if (i < this._results.length) result = this._results[i];
if (result) {
const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
- this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
+ const lines = new List<string>(result[2]);
+ result[0].lines = lines;
+ result[0].highlighting = highlights.join(", ");
+ highlights.forEach((item) => headers.add(item));
+ this._visibleDocuments[i] = result[0];
this._isSearch[i] = "search";
+ Doc.BrushDoc(result[0]);
+ result[0].searchMatch = true;
+ Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]);
+ this.children++;
}
}
else {
result = this._results[i];
if (result) {
const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
- this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
- this._isSearch[i] = "search";
+ 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++;
+ }
}
}
}
}
}
+ const schemaheaders: SchemaHeaderField[] = [];
+ this.headerscale = headers.size;
+ headers.forEach((item) => schemaheaders.push(new SchemaHeaderField(item, "#f1efeb")));
+ this.headercount = schemaheaders.length;
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>(schemaheaders);
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; }
@@ -504,165 +727,197 @@ export class SearchBox extends React.Component<SearchProps> {
@computed
get resultHeight() { return this._numTotalResults * 70; }
- //if true, any keywords can be used. if false, all keywords are required.
- @action.bound
- handleWordQueryChange = () => {
- this._basicWordStatus = !this._basicWordStatus;
- }
-
- @action.bound
- handleNodeChange = () => {
- this._nodeStatus = !this._nodeStatus;
- if (this._nodeStatus) {
- this.expandSection(`node${this.props.id}`);
- }
- else {
- this.collapseSection(`node${this.props.id}`);
- }
- }
-
- @action.bound
- handleKeyChange = () => {
- this._keyStatus = !this._keyStatus;
- if (this._keyStatus) {
- this.expandSection(`key${this.props.id}`);
- }
- else {
- this.collapseSection(`key${this.props.id}`);
- }
- }
-
- @action.bound
- handleFilterChange = () => {
- this._filterOpen = !this._filterOpen;
- if (this._filterOpen) {
- this.expandSection(`filterhead${this.props.id}`);
- document.getElementById(`filterhead${this.props.id}`)!.style.padding = "5";
- }
- else {
- this.collapseSection(`filterhead${this.props.id}`);
+ 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 = () => {
+ return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
}
-
- @computed
- get menuHeight() {
- return document.getElementById("hi")?.clientHeight;
+ panelHeight = () => {
+ return this.props.PanelHeight();
}
-
-
- collapseSection(thing: string) {
- const id = this.props.id;
- const element = document.getElementById(thing)!;
- // get the height of the element's inner content, regardless of its actual size
- const sectionHeight = element.scrollHeight;
-
- // temporarily disable all css transitions
- const elementTransition = element.style.transition;
- element.style.transition = '';
-
- // on the next frame (as soon as the previous style change has taken effect),
- // explicitly set the element's height to its current pixel height, so we
- // aren't transitioning out of 'auto'
- requestAnimationFrame(function () {
- element.style.height = sectionHeight + 'px';
- element.style.transition = elementTransition;
-
- // on the next frame (as soon as the previous style change has taken effect),
- // have the element transition to height: 0
- requestAnimationFrame(function () {
- element.style.height = 0 + 'px';
- thing === `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null;
- });
- });
-
- // mark the section as "currently collapsed"
- element.setAttribute('data-collapsed', 'true');
- }
-
- expandSection(thing: string) {
- const element = document.getElementById(thing)!;
- // get the height of the element's inner content, regardless of its actual size
- const sectionHeight = element.scrollHeight;
-
- // have the element transition to the height of its inner content
- element.style.height = sectionHeight + 'px';
-
- // when the next css transition finishes (which should be the one we just triggered)
- element.addEventListener('transitionend', function handler(e) {
- // remove this event listener so it only gets triggered once
- element.removeEventListener('transitionend', handler);
-
- // remove "height" from the element's inline styles, so it can return to its initial value
- element.style.height = "auto";
- //element.style.height = undefined;
- });
-
- // mark the section as "currently not collapsed"
- element.setAttribute('data-collapsed', 'false');
-
+ selectElement = (doc: Doc) => {
+ //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex));
}
- autoset(thing: string) {
- const element = document.getElementById(thing)!;
- element.removeEventListener('transitionend', function (e) { });
-
- // remove "height" from the element's inline styles, so it can return to its initial value
- element.style.height = "auto";
- //element.style.height = undefined;
+ addDocument = (doc: Doc) => {
+ return null;
}
- @action.bound
- updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; }
-
- @action.bound
- updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; }
-
- @action.bound
- updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; }
+ @observable filter = false;
+ //Make id layour document
render() {
-
+ this.props.Document._chromeStatus === "disabled";
+ this.props.Document._searchDoc = true;
+ const cols = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length;
+ let length = 0;
+ cols > 5 ? length = 1076 : length = cols * 205 + 51;
+ let height = 0;
+ const rows = this.children;
+ rows > 8 ? height = 31 + 31 * 8 : height = 31 * rows + 31;
return (
- <div className="searchBox-container">
+ <div style={{ pointerEvents: "all" }} className="searchBox-container">
<div className="searchBox-bar">
- <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, () => this._searchString ? this.startDragCollection() : undefined)} ref={this.collectionRef} title="Drag Results as Collection">
- <FontAwesomeIcon icon="object-group" size="lg" />
- </span>
- <input value={this._searchString} 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={{ width: this._searchbarOpen ? "500px" : "100px" }} />
- <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => this.handleFilterChange()}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button>
- </div>
-
- <div id={`filterhead${this.props.id}`} className="filter-form" >
- <div id={`filterhead2${this.props.id}`} className="filter-header" style={this._filterOpen ? {} : {}}>
- <button className="filter-item" style={this._basicWordStatus ? { background: "#aaaaa3", } : {}} onClick={this.handleWordQueryChange}>Keywords</button>
- <button className="filter-item" style={this._keyStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleKeyChange}>Keys</button>
- <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button>
- </div>
- <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
- <IconBar setIcons={(icons: string[]) => {
- this._icons = icons;
- }} />
- </div>
- <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
- <div className="filter-keybar">
- <button className="filter-item" style={this._titleFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateTitleStatus}>Title</button>
- <button className="filter-item" style={this._deletedDocsStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateDataStatus}>Deleted Docs</button>
- <button className="filter-item" style={this._authorFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateAuthorStatus}>Author</button>
+ <div style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div>
+ <div style={{ display: "flex", alignItems: "center" }}>
+ <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg"
+ style={{ color: "black", padding: 1, left: 35, position: "relative" }} />
+
+ <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={{ 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(() => {
+ const dofilter = (currentSelectedCollection: DocumentView) => {
+ let docs = DocListCast(currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(currentSelectedCollection.dataDoc)]);
+ while (docs.length > 0) {
+ const newarray: Doc[] = [];
+ docs.filter(d => d.data !== undefined).forEach((d) => {
+ d._searchDocs = new List<Doc>(this.docsforfilter);
+ newarray.push(...DocListCast(d.data));
+ });
+ docs = newarray;
+ }
+ };
+ this.filter = !this.filter && !this.searchFullDB;
+ if (this.filter === true && this.currentSelectedCollection !== undefined) {
+ this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter);
+
+ dofilter(this.currentSelectedCollection);
+
+ this.currentSelectedCollection.props.Document._docFilters = new List<string>(Cast(this.props.Document._docFilters, listSpec("string"), []));
+ this.props.Document.selectedDoc = this.currentSelectedCollection.props.Document;
+ }
+ else if (this.filter === false && this.currentSelectedCollection !== undefined) {
+
+ dofilter(this.currentSelectedCollection);
+
+ this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]);
+ this.currentSelectedCollection.props.Document._docFilters = undefined;
+ 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>();
+ const newdocs = DocListCast(d.data);
+ newdocs.forEach((newdoc) => {
+ newarray.push(newdoc);
+ });
+ }
+ });
+ docs = newarray;
+ }
+ this.currentSelectedCollection.props.Document._docFilters = undefined;
+ 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>();
+ const newdocs = DocListCast(d.data);
+ newdocs.forEach((newdoc) => {
+ newarray.push(newdoc);
+ });
+ }
+ });
+ docs = newarray;
+ }
+ this.currentSelectedCollection.props.Document._docFilters = undefined;
+ this.currentSelectedCollection.props.Document._searchDocs = undefined;
+ this.currentSelectedCollection = undefined;
+ }
+ this.submitSearch();
+ });
+ }} />
+ DB
+ </label>
+ </div>
+ </div>
+ </form>
</div>
</div>
+
+ </div>
+ <div style={{ zIndex: 20000, color: "black" }}>
+ {this._searchbarOpen === true ?
+ <div style={{ display: "flex", justifyContent: "center", }}>
+ {this.noresults === "" ? <div style={{ display: this.open === true ? "flex" : "none", overflow: "auto", }}>
+ <CollectionView {...this.props}
+ Document={this.props.Document}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ PanelHeight={this.open === true ? () => height : () => 0}
+ PanelWidth={this.open === true ? () => length : () => 0}
+ overflow={cols > 5 || rows > 8 ? 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}>
- {this._visibleElements}
</div>
- </div>
+ </div >
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
deleted file mode 100644
index 469f062b2..000000000
--- a/src/client/views/search/SearchItem.scss
+++ /dev/null
@@ -1,163 +0,0 @@
-@import "../globalCssVariables";
-
-.searchItem-overview {
- display: flex;
- flex-direction: reverse;
- justify-content: flex-end;
- z-index: 0;
-}
-
-.searchBox-placeholder,
-.searchItem-overview .searchItem {
- width: 100%;
- background: $light-color-secondary;
- border-color: $intermediate-color;
- border-bottom-style: solid;
- padding: 10px;
- min-height: 50px;
- max-height: 150px;
- height: auto;
- z-index: 0;
- display: flex;
- overflow: visible;
-
- .searchItem-body {
- display: flex;
- flex-direction: row;
- width: 100%;
-
- .searchItem-title-container {
- width: 100%;
- overflow: hidden;
-
- .searchItem-title {
- text-transform: uppercase;
- text-align: left;
- width: 100%;
- font-weight: bold;
- }
- }
-
- .searchItem-info {
- display: flex;
- justify-content: flex-end;
-
- .icon-icons {
- width: 50px
- }
-
- .icon-live {
- width: 175px;
- height: 0px;
- }
-
- .icon-icons {
- height:auto;
- }
- .icon-icons,
- .icon-live {
- margin: auto;
- overflow: visible;
-
- .searchItem-type {
- display: inline-block;
- width: 100%;
- position: absolute;
- justify-content: center;
- align-items: center;
- position: relative;
- margin-right: 5px;
- }
-
- .pdfBox-cont {
- overflow: hidden;
-
- img {
- width: 100% !important;
- height: auto !important;
- }
- }
-
- .searchItem-type:hover+.searchItem-label {
- opacity: 1;
- }
-
- .searchItem-label {
- font-size: 10;
- position: relative;
- right: 0px;
- text-transform: capitalize;
- opacity: 0;
- -webkit-transition: opacity 0.2s ease-in-out;
- -moz-transition: opacity 0.2s ease-in-out;
- -o-transition: opacity 0.2s ease-in-out;
- transition: opacity 0.2s ease-in-out;
- }
- }
-
- .icon-live:hover {
- .pdfBox-cont {
- img {
- width: 100% !important;
- }
- }
- }
- }
-
- .searchItem-info:hover {
- width: 60%;
- }
- }
-}
-
-.searchItem:hover~.searchBox-instances,
-.searchBox-instances:hover,
-.searchBox-instances:active {
- opacity: 1;
- background: $lighter-alt-accent;
-}
-
-.searchItem:hover {
- transition: all 0.2s;
- background: $lighter-alt-accent;
-}
-
-.searchItem-highlighting {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: pre;
-}
-
-.searchBox-instances {
- opacity: 1;
- width:40px;
- height:40px;
- background: gray;
- transition: all 0.2s ease;
- color: black;
- overflow: hidden;
- right:-100;
- display:inline-block;
-}
-
-
-.searchItem-overview:hover {
- z-index: 1;
-}
-
-.searchBox-placeholder {
- min-height: 50px;
- margin-left: 150px;
- width: calc(100% - 150px);
- text-transform: uppercase;
- text-align: left;
- font-weight: bold;
-}
-
-.collection {
- display: flex;
-}
-
-.collection-item {
- width: 35px;
-} \ No newline at end of file
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
deleted file mode 100644
index 2436bf418..000000000
--- a/src/client/views/search/SearchItem.tsx
+++ /dev/null
@@ -1,310 +0,0 @@
-import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero, returnEmptyString, returnEmptyFilter } from "../../../Utils";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager, SetupDrag } from "../../util/DragManager";
-import { SearchUtil } from "../../util/SearchUtil";
-import { Transform } from "../../util/Transform";
-import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionViewType } from "../collections/CollectionView";
-import { ParentDocSelector } from "../collections/ParentDocumentSelector";
-import { ContextMenu } from "../ContextMenu";
-import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { SearchBox } from "./SearchBox";
-import "./SearchItem.scss";
-import "./SelectorContextMenu.scss";
-
-export interface SearchItemProps {
- doc: Doc;
- query: string;
- highlighting: string[];
- lines: string[];
-}
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFile);
-library.add(faFilePdf);
-library.add(faFilm);
-library.add(faMusic);
-library.add(faLink);
-library.add(faChartBar);
-library.add(faGlobeAsia, faFingerprint);
-
-@observer
-export class SelectorContextMenu extends React.Component<SearchItemProps> {
- @observable private _docs: { col: Doc, target: Doc }[] = [];
- @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
-
- constructor(props: SearchItemProps) {
- super(props);
- this.fetchDocuments();
- }
-
- async fetchDocuments() {
- const aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc);
- const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` });
- const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
- allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
- docs.forEach(doc => map.delete(doc));
- runInAction(() => {
- this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc }));
- this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
-
- });
- }
-
- 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;
- }
- CollectionDockingView.AddRightSplit(col);
- };
- }
- render() {
- return (
- <div className="parents">
- <p className="contexts">Contexts:</p>
- {[...this._docs, ...this._otherDocs].map(doc => {
- const item = React.createRef<HTMLDivElement>();
- return <div className="collection" key={doc.col[Id] + doc.target[Id]} ref={item}>
- <div className="collection-item" onPointerDown={
- SetupDrag(item, () => doc.col, undefined, undefined, () => SearchBox.Instance.closeSearch())}>
- <FontAwesomeIcon icon={faStickyNote} />
- </div>
- <a onClick={this.getOnClick(doc)}>{doc.col.title}</a>
- </div>;
- })}
- </div>
- );
- }
-}
-
-export interface LinkMenuProps {
- doc1: Doc;
- doc2: Doc;
-}
-
-@observer
-export class LinkContextMenu extends React.Component<LinkMenuProps> {
-
- highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc);
-
- unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc);
-
- getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col);
-
- render() {
- return (
- <div className="parents">
- <p className="contexts">Anchors:</p>
- <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div>
- <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div>
- </div>
- );
- }
-
-}
-
-@observer
-export class SearchItem extends React.Component<SearchItemProps> {
-
- @observable _selected: boolean = false;
-
- onClick = () => {
- // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like
- DocumentManager.Instance.jumpToDocument(this.props.doc, false);
- }
- @observable _useIcons = true;
- @observable _displayDim = 50;
-
- componentDidMount() {
- Doc.SetSearchQuery(this.props.query);
- this.props.doc.searchMatch = true;
- }
- componentWillUnmount() {
- this.props.doc.searchMatch = undefined;
- }
-
- //@computed
- @action
- public DocumentIcon() {
- const layoutresult = StrCast(this.props.doc.type);
- if (!this._useIcons) {
- const returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
- const returnYDimension = () => this._displayDim;
- const docview = <div
- onPointerDown={action(() => {
- this._useIcons = !this._useIcons;
- this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
- })}
- onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} >
- <ContentFittingDocumentView
- Document={this.props.doc}
- LibraryPath={emptyPath}
- rootSelected={returnFalse}
- fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- docFilters={returnEmptyFilter}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- ScreenToLocalTransform={Transform.Identity}
- renderDepth={1}
- PanelWidth={returnXDimension}
- PanelHeight={returnYDimension}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- focus={emptyFunction}
- moveDocument={returnFalse}
- parentActive={returnFalse}
- whenActiveChanged={returnFalse}
- bringToFront={returnFalse}
- ContentScaling={returnOne}
- />
- </div>;
- return docview;
- }
- const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
- layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
- layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote :
- layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm :
- layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup :
- layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic :
- layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink :
- layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
- faCaretUp;
- return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} >
- <FontAwesomeIcon icon={button} size="2x" />
- </div>;
- }
-
- collectionRef = React.createRef<HTMLDivElement>();
-
- @action
- pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
-
- nextHighlight = (e: React.PointerEvent) => {
- e.preventDefault();
- e.button === 0 && SearchBox.Instance.openSearch(e);
- this.props.doc.searchMatch = false;
- setTimeout(() => this.props.doc.searchMatch = true, 0);
- }
- highlightDoc = (e: React.PointerEvent) => {
- if (this.props.doc.type === DocumentType.LINK) {
- if (this.props.doc.anchor1 && this.props.doc.anchor2) {
-
- const doc1 = Cast(this.props.doc.anchor1, Doc, null);
- const doc2 = Cast(this.props.doc.anchor2, Doc, null);
- Doc.BrushDoc(doc1);
- Doc.BrushDoc(doc2);
- }
- } else {
- Doc.BrushDoc(this.props.doc);
- }
- e.stopPropagation();
- }
-
- unHighlightDoc = (e: React.PointerEvent) => {
- if (this.props.doc.type === DocumentType.LINK) {
- if (this.props.doc.anchor1 && this.props.doc.anchor2) {
-
- const doc1 = Cast(this.props.doc.anchor1, Doc, null);
- const doc2 = Cast(this.props.doc.anchor2, Doc, null);
- Doc.UnBrushDoc(doc1);
- Doc.UnBrushDoc(doc2);
- }
- } else {
- Doc.UnBrushDoc(this.props.doc);
- }
- }
-
- onContextMenu = (e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({
- description: "Copy ID", event: () => {
- Utils.CopyText(this.props.doc[Id]);
- },
- icon: "fingerprint"
- });
- ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
- }
-
- _downX = 0;
- _downY = 0;
- _target: any;
- onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- e.stopPropagation();
- this._target = e.currentTarget;
- document.removeEventListener("pointermove", this.onPointerMoved);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMoved);
- document.addEventListener("pointerup", this.onPointerUp);
- }
- onPointerMoved = (e: PointerEvent) => {
- if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD ||
- Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) {
- document.removeEventListener("pointermove", this.onPointerMoved);
- document.removeEventListener("pointerup", this.onPointerUp);
- const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
- DragManager.StartDocumentDrag([this._target], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
- }
- }
- onPointerUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.onPointerMoved);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
-
- @computed
- get contextButton() {
- return <ParentDocSelector Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
- }
-
- render() {
- const doc1 = Cast(this.props.doc.anchor1, Doc);
- const doc2 = Cast(this.props.doc.anchor2, Doc);
- return <div className="searchItem-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}>
- <div className="searchItem" onPointerDown={this.nextHighlight} onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc}>
- <div className="searchItem-body" onClick={this.onClick}>
- <div className="searchItem-title-container">
- <div className="searchItem-title">{StrCast(this.props.doc.title)}</div>
- <div className="searchItem-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div>
- {this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="searchItem-highlighting">`${l}`</div>)}
- </div>
- </div>
- <div className="searchItem-info" style={{ width: this._useIcons ? "30px" : "100%" }}>
- <div className={`icon-${this._useIcons ? "icons" : "live"}`}>
- <div className="searchItem-type" title="Click to Preview" onPointerDown={this.onPointerDown}>{this.DocumentIcon()}</div>
- <div className="searchItem-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div>
- </div>
- </div>
- <div className="searchItem-context" title="Drag as document">
- {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> :
- this.contextButton}
- </div>
- </div>
- </div>;
- }
-} \ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 87acb2ea7..6bfe91378 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -409,7 +409,7 @@ export namespace Doc {
// and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
export function GetProto(doc: Doc): Doc {
if (doc instanceof Promise) {
- console.log("GetProto: error: got Promise insead of Doc");
+ console.log("GetProto: warning: got Promise insead of Doc");
}
const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
return proto === doc ? proto : Doc.GetProto(proto);
@@ -508,6 +508,10 @@ export namespace Doc {
alias.aliasOf = doc;
alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`);
alias.author = Doc.CurrentUserEmail;
+ alias[AclSym] = doc[AclSym];
+
+ Doc.AddDocToList(doc[DataSym], "aliases", alias);
+
return alias;
}
@@ -524,10 +528,10 @@ export namespace Doc {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
const copyObjectField = async (field: ObjectField) => {
- const list = await Cast(doc[key], listSpec(Doc));
+ const list = Cast(doc[key], listSpec(Doc));
const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => await Doc.makeClone(d as Doc, cloneMap, rtfs, exclusions, dontCreate)));
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, rtfs, exclusions, dontCreate)));
!dontCreate && assignKey(new List<Doc>(clones));
} else if (doc[key] instanceof Doc) {
assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions, dontCreate)); // reference documents except copy documents that are expanded teplate fields
@@ -587,6 +591,11 @@ export namespace Doc {
}
export async function Zip(doc: Doc) {
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
const { clone, map } = await Doc.MakeClone(doc, true);
function replacer(key: any, value: any) {
if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
@@ -616,9 +625,9 @@ export namespace Doc {
Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]);
const docString = JSON.stringify({ id: doc[Id], docs }, replacer);
- var zip = new JSZip();
+ const zip = new JSZip();
- zip.file("doc.json", docString);
+ zip.file(doc.title + ".json", docString);
// // Generate a directory within the Zip file structure
// var img = zip.folder("images");
@@ -630,7 +639,7 @@ export namespace Doc {
zip.generateAsync({ type: "blob" })
.then((content: any) => {
// Force down of the Zip file
- saveAs(content, "download.zip");
+ saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title?
});
}
//
@@ -1257,4 +1266,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
return docs.length ? new List(docs) : prevValue;
});
Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "match" | "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); });
-Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); }); \ No newline at end of file
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 7cfd74cc4..dbe51b24a 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -2,6 +2,7 @@ import { Deserializable } from "../client/util/SerializationHelper";
import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
import { ObjectField } from "./ObjectField";
import { Copy, ToScriptString, ToString, Update } from "./FieldSymbols";
+import { Scripting } from "../client/util/Scripting";
export enum InkTool {
None = "none",
@@ -44,9 +45,11 @@ export class InkField extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}} `) + "])";
}
[ToString]() {
return "InkField";
}
}
+
+Scripting.addGlobal("InkField", InkField); \ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 5cf0e0cc3..2ca5ac082 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -20,7 +20,7 @@ export class RichTextField extends ObjectField {
}
Empty() {
- return !(this.Text || this.Data.toString().includes("dashField"));
+ return !(this.Text || this.Data.toString().includes("dashField") || this.Data.toString().includes("align"));
}
[Copy]() {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index bd08b2f32..f55483a5b 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -183,6 +183,10 @@ Scripting.addGlobal(function getIndexVal(list: any[], index: number) {
return list.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);
}, "returns the value at a given index of a list", "(list: any[], index: number)");
+Scripting.addGlobal(function makeScript(script: string) {
+ return ScriptField.MakeScript(script);
+}, "returns the value at a given index of a list", "(list: any[], index: number)");
+
export namespace ComputedField {
let useComputed = true;
export function DisableComputedFields() {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index c77d80d76..ada13226e 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -94,6 +94,7 @@ export const documentSchema = createSchema({
followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, )
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)
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a62795e64..4c71572db 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
@@ -74,7 +74,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !DocServer.Control.isReadOnly();// && !playgroundMode;
if (writeToDoc) {
if (value === undefined) {
@@ -115,22 +115,34 @@ export function OVERRIDE_ACL(val: boolean) {
_overrideAcl = val;
}
-let playgroundMode = false;
+// playground mode allows the user to add/delete documents or make layout changes without them saving to the server
+// let playgroundMode = false;
-export function togglePlaygroundMode() {
- playgroundMode = !playgroundMode;
-}
-
-export function getPlaygroundMode() {
- return playgroundMode;
-}
+// export function togglePlaygroundMode() {
+// playgroundMode = !playgroundMode;
+// }
+// the list of groups that the current user is a member of
let currentUserGroups: string[] = [];
+// called from GroupManager once the groups have been fetched from the server
export function setGroups(groups: string[]) {
currentUserGroups = groups;
}
+/**
+ * These are the various levels of access a user can have to a document.
+ *
+ * Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
+ *
+ * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
+ *
+ * Add: a user with add access to a document can add documents/annotations to that document but cannot edit or delete anything.
+ *
+ * View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
+ *
+ * None: the document is not shared with that user.
+ */
export enum SharingPermissions {
Admin = "Admin",
Edit = "Can Edit",
@@ -139,18 +151,23 @@ export enum SharingPermissions {
None = "Not Shared"
}
+/**
+ * Calculates the effective access right to a document for the current user.
+ */
export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
+ if (!target) return AclPrivate;
if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
if (target[AclSym] && Object.keys(target[AclSym]).length) {
- if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;
+ // if the current user is the author of the document / the current user is a member of the admin group
+ // but not if the doc in question is an alias - the current user will be the author of their alias rather than the original author
+ if ((Doc.CurrentUserEmail === (target.__fields?.author || target.author) && !(target.aliasOf || target.__fields?.aliasOf)) || 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;
let effectiveAcl = AclPrivate;
- let aclPresent = false;
-
const HierarchyMapping = new Map<symbol, number>([
[AclPrivate, 0],
[AclReadonly, 1],
@@ -160,19 +177,28 @@ 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("_", ".")) {
- if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
- aclPresent = true;
+ if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
effectiveAcl = value as symbol;
- if (effectiveAcl === AclEdit) break;
+ if (effectiveAcl === AclAdmin) break;
}
}
}
- return aclPresent ? effectiveAcl : AclEdit;
+ // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl)
+ return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)! < 3 ? AclEdit : effectiveAcl;
}
return AclAdmin;
}
-
+/**
+ * Recursively distributes the access right for a user across the children of a document and its annotations.
+ * @param key the key storing the access right (e.g. ACL-groupname)
+ * @param acl the access right being stored (e.g. "Can Edit")
+ * @param target the document on which this access right is being set
+ * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the ACLs from the collection)
+ * inheritingFromCollection is not currently being used but could be used if ACL assignment defaults change
+ */
export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
const HierarchyMapping = new Map<string, number>([
@@ -183,37 +209,51 @@ 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])
const dataDoc = target[DataSym];
- if (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!) target[key] = acl;
+ // 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;
+
+ // maps over the aliases of the document
+ if (target.aliases) {
+ DocListCast(target.aliases).map(alias => {
+ 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)!)) {
- distributeAcls(key, acl, d);
- d[key] = 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)!)) {
- distributeAcls(key, acl, data);
- data[key] = 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)!)) {
- distributeAcls(key, acl, d);
- d[key] = 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)!)) {
- distributeAcls(key, acl, data);
- data[key] = acl;
+ distributeAcls(key, acl, data, inheritingFromCollection);
}
});
}
+
+ changed && fetchProto(target); // updates target[AclSym] when changes to acls have been made
}
const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
@@ -223,6 +263,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
const effectiveAcl = GetEffectiveAcl(target, in_prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
+ // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
// if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index b75102e43..8ae504f1b 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -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().rightSidebarCollection, Doc) as Doc;
+ const audioRightSidebar = Cast(Doc.UserDoc()["sidebar-sharing"], Doc) as Doc;
const audioDoc = this._audioCol;
const data = Cast(audioRightSidebar.data, listSpec(Doc));
for (let i = 1; i < 8; i++) {
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 3a889b0db..40aa65372 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -436,7 +436,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().rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
+ this._activeDoc !== Doc.UserDoc()["sidebar-sharing"] && this._activeDoc.title !== "WORKSPACES") {
return (
<div className="docButton"
style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
@@ -454,7 +454,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().rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
+ this._activeDoc !== Doc.UserDoc()["sidebar-sharing"] && this._activeDoc.title !== "WORKSPACES") {
return (
<div className="docButton"
style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
@@ -605,7 +605,7 @@ export class MobileInterface extends React.Component {
// Returns the image upload pop up
@computed get uploadImage() {
- const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.UserDoc()["sidebar-sharing"], Doc) as Doc;
return <Uploader Document={doc} />;
}
@@ -628,7 +628,7 @@ export class MobileInterface extends React.Component {
*/
@action
switchToMobileUploads = () => {
- const mobileUpload = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ const mobileUpload = Cast(Doc.UserDoc()["sidebar-sharing"], Doc) as Doc;
this.switchCurrentView(mobileUpload);
this._homeMenu = false;
}
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 60f66c878..fd9bc0c83 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -1,11 +1,11 @@
-import { readFile, writeFile, exists, mkdir, unlink, createWriteStream } from 'fs';
-import { ExecOptions } from 'shelljs';
import { exec } from 'child_process';
-import * as path from 'path';
-import * as rimraf from "rimraf";
-import { yellow, Color } from 'colors';
+import { Color, yellow } from 'colors';
+import { createWriteStream, exists, mkdir, readFile, unlink, writeFile } from 'fs';
import * as nodemailer from "nodemailer";
import { MailOptions } from "nodemailer/lib/json-transport";
+import * as path from 'path';
+import * as rimraf from "rimraf";
+import { ExecOptions } from 'shelljs';
import Mail = require('nodemailer/lib/mailer');
const projectRoot = path.resolve(__dirname, "../../");
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 753c31fcf..7251e07a1 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -176,7 +176,7 @@ export namespace SolrManager {
"audio": ["_t", "url"],
"web": ["_t", "url"],
"date": ["_d", value => new Date(value.date).toISOString()],
- "proxy": ["_i", "fieldId"],
+ // "proxy": ["_i", "fieldId"],
"list": ["_l", list => {
const results = [];
for (const value of list.fields) {
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 4455d11eb..515fbe4ff 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -14,7 +14,6 @@ import { normalize } from "path";
import RouteSubscriber from "../RouteSubscriber";
const imageDataUri = require('image-data-uri');
import { isWebUri } from "valid-url";
-import { launch } from "puppeteer";
import { Opt } from "../../fields/Doc";
import { SolrManager } from "./SearchManager";
@@ -281,25 +280,26 @@ function delay(ms: number) {
*
* On failure, returns undefined.
*/
-async function captureYoutubeScreenshot(targetUrl: string): Promise<Opt<Buffer>> {
- const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
- const page = await browser.newPage();
- await page.setViewport({ width: 1920, height: 1080 });
+async function captureYoutubeScreenshot(targetUrl: string){
+ // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
+ // const page = await browser.newPage();
+ // // await page.setViewport({ width: 1920, height: 1080 });
- await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any });
+ // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any });
- const videoPlayer = await page.$('.html5-video-player');
- videoPlayer && await page.focus("video");
- await delay(7000);
- const ad = await page.$('.ytp-ad-skip-button-text');
- await ad?.click();
- await videoPlayer?.click();
- await delay(1000);
- // hide youtube player controls.
- await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none');
+ // const videoPlayer = await page.$('.html5-video-player');
+ // videoPlayer && await page.focus("video");
+ // await delay(7000);
+ // const ad = await page.$('.ytp-ad-skip-button-text');
+ // await ad?.click();
+ // await videoPlayer?.click();
+ // await delay(1000);
+ // // hide youtube player controls.
+ // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none');
- const buffer = await videoPlayer?.screenshot({ encoding: "binary" });
- await browser.close();
+ // const buffer = await videoPlayer?.screenshot({ encoding: "binary" });
+ // await browser.close();
- return buffer;
+ // return buffer;
+ return null;
} \ No newline at end of file
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index e2cd88726..e657866ce 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -6,7 +6,6 @@ import { exec } from 'child_process';
// const recommender = new Recommender();
// recommender.testModel();
-import executeImport from "../../scraping/buxton/final/BuxtonImporter";
export default class UtilManager extends ApiManager {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 2bf4c1956..890fb6f6d 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -1,23 +1,22 @@
-import { unlinkSync, createWriteStream, readFileSync, rename, writeFile, existsSync } from 'fs';
-import { Utils } from '../Utils';
-import * as path from 'path';
-import * as sharp from 'sharp';
-import request = require('request-promise');
+import { red } from 'colors';
import { ExifImage } from 'exif';
-import { Opt } from '../fields/Doc';
-import { AcceptibleMedia, Upload } from './SharedMediaTypes';
-import { filesDirectory, publicDirectory } from '.';
import { File } from 'formidable';
+import { createWriteStream, existsSync, readFileSync, rename, unlinkSync, writeFile } from 'fs';
+import * as path from 'path';
import { basename } from "path";
-import { createIfNotExists } from './ActionUtilities';
+import * as sharp from 'sharp';
+import { Stream } from 'stream';
+import { filesDirectory, publicDirectory } from '.';
+import { Opt } from '../fields/Doc';
import { ParsedPDF } from "../server/PdfTypes";
+import { Utils } from '../Utils';
+import { createIfNotExists } from './ActionUtilities';
+import { clientPathToFile, Directory, pathToDirectory, serverPathToFile } from './ApiManagers/UploadManager';
+import { resolvedServerUrl } from "./server_Initialization";
+import { AcceptibleMedia, Upload } from './SharedMediaTypes';
+import request = require('request-promise');
const parse = require('pdf-parse');
-import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager';
-import { red } from 'colors';
-import { Stream } from 'stream';
-import { resolvedPorts } from './server_Initialization';
const requestImageSize = require("../client/util/request-image-size");
-import { resolvedServerUrl } from "./server_Initialization";
export enum SizeSuffix {
Small = "_s",
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 24745cbb4..a9a3b0481 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -1,9 +1,9 @@
-import { Database } from './database';
-
-import * as path from 'path';
import * as fs from 'fs';
+import * as path from 'path';
+import { Database } from './database';
import { Search } from './Search';
+
function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
for (const key in doc) {
if (!doc.hasOwnProperty(key)) {
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index 1f1d702d9..d2d8bb3b3 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -1,6 +1,6 @@
-import { IDatabase, DocumentsCollection } from './IDatabase';
-import { Transferable } from './Message';
import * as mongodb from 'mongodb';
+import { DocumentsCollection, IDatabase } from './IDatabase';
+import { Transferable } from './Message';
export class MemoryDatabase implements IDatabase {
diff --git a/src/server/Message.ts b/src/server/Message.ts
index ff0381fd3..59b24cd82 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,6 +1,6 @@
-import { Utils } from "../Utils";
import { Point } from "../pen-gestures/ndollar";
import { AnalysisResult, ImportResults } from "../scraping/buxton/final/BuxtonImporter";
+import { Utils } from "../Utils";
export class Message<T> {
private _name: string;
diff --git a/src/server/ProcessFactory.ts b/src/server/ProcessFactory.ts
index acb8b3a99..63682368f 100644
--- a/src/server/ProcessFactory.ts
+++ b/src/server/ProcessFactory.ts
@@ -1,8 +1,8 @@
-import { existsSync, mkdirSync } from "fs";
-import { pathFromRoot, fileDescriptorFromStream } from './ActionUtilities';
-import rimraf = require("rimraf");
import { ChildProcess, spawn, StdioOptions } from "child_process";
+import { existsSync, mkdirSync } from "fs";
import { Stream } from "stream";
+import { fileDescriptorFromStream, pathFromRoot } from './ActionUtilities';
+import rimraf = require("rimraf");
export namespace ProcessFactory {
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
deleted file mode 100644
index 935ec3871..000000000
--- a/src/server/Recommender.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-// //import { Doc } from "../fields/Doc";
-// //import { StrCast } from "../fields/Types";
-// //import { List } from "../fields/List";
-// //import { CognitiveServices } from "../client/cognitive_services/CognitiveServices";
-
-// // var w2v = require('word2vec');
-// var assert = require('assert');
-// var arxivapi = require('arxiv-api-node');
-// import requestPromise = require("request-promise");
-// import * as use from '@tensorflow-models/universal-sentence-encoder';
-// import { Tensor } from "@tensorflow/tfjs-core/dist/tensor";
-// require('@tensorflow/tfjs-node');
-
-// //http://gnuwin32.sourceforge.net/packages/make.htm
-
-// export class Recommender {
-
-// private _model: any;
-// static Instance: Recommender;
-// private dimension: number = 0;
-// private choice: string = ""; // Tensorflow or Word2Vec
-
-// constructor() {
-// Recommender.Instance = this;
-// }
-
-// /***
-// * Loads pre-trained model from TF
-// */
-
-// public async loadTFModel() {
-// let self = this;
-// return new Promise(res => {
-// use.load().then(model => {
-// self.choice = "TF";
-// self._model = model;
-// self.dimension = 512;
-// res(model);
-// });
-// }
-
-// );
-// }
-
-// /***
-// * Loads pre-trained model from word2vec
-// */
-
-// // private loadModel(): Promise<any> {
-// // let self = this;
-// // return new Promise(res => {
-// // w2v.loadModel("./node_modules/word2vec/examples/fixtures/vectors.txt", function (err: any, model: any) {
-// // self.choice = "WV";
-// // self._model = model;
-// // self.dimension = model.size;
-// // res(model);
-// // });
-// // });
-// // }
-
-// /***
-// * Testing
-// */
-
-// public async testModel() {
-// if (!this._model) {
-// await this.loadTFModel();
-// }
-// if (this._model) {
-// if (this.choice === "WV") {
-// let similarity = this._model.similarity('father', 'mother');
-// }
-// else if (this.choice === "TF") {
-// const model = this._model as use.UniversalSentenceEncoder;
-// // Embed an array of sentences.
-// const sentences = [
-// 'Hello.',
-// 'How are you?'
-// ];
-// const embeddings = await this.vectorize(sentences);
-// if (embeddings) embeddings.print(true /*verbose*/);
-// // model.embed(sentences).then(embeddings => {
-// // // `embeddings` is a 2D tensor consisting of the 512-dimensional embeddings for each sentence.
-// // // So in this example `embeddings` has the shape [2, 512].
-// // embeddings.print(true /* verbose */);
-// // });
-// }
-// }
-// else {
-// console.log("model not found :(");
-// }
-// }
-
-// /***
-// * Uses model to convert words to vectors
-// */
-
-// public async vectorize(text: string[]): Promise<Tensor | undefined> {
-// if (!this._model) {
-// await this.loadTFModel();
-// }
-// if (this._model) {
-// if (this.choice === "WV") {
-// let word_vecs = this._model.getVectors(text);
-// return word_vecs;
-// }
-// else if (this.choice === "TF") {
-// const model = this._model as use.UniversalSentenceEncoder;
-// return new Promise<Tensor>(res => {
-// model.embed(text).then(embeddings => {
-// res(embeddings);
-// });
-// });
-
-// }
-// }
-// }
-
-// // public async trainModel() {
-// // w2v.word2vec("./node_modules/word2vec/examples/eng_news-typical_2016_1M-sentences.txt", './node_modules/word2vec/examples/my_phrases.txt', {
-// // cbow: 1,
-// // size: 200,
-// // window: 8,
-// // negative: 25,
-// // hs: 0,
-// // sample: 1e-4,
-// // threads: 20,
-// // iter: 200,
-// // minCount: 2
-// // });
-// // }
-
-// }
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 1a2340afc..78b75d6be 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,8 +1,8 @@
-import RouteSubscriber from "./RouteSubscriber";
-import { DashUserModel } from "./authentication/DashUserModel";
-import { Request, Response, Express } from 'express';
-import { cyan, red, green } from 'colors';
+import { cyan, green, red } from 'colors';
+import { Express, Request, Response } from 'express';
import { AdminPriviliges } from ".";
+import { DashUserModel } from "./authentication/DashUserModel";
+import RouteSubscriber from "./RouteSubscriber";
export enum Method {
GET,
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 21064e520..decd1f5b1 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -1,5 +1,5 @@
-import * as rp from 'request-promise';
import { red } from 'colors';
+import * as rp from 'request-promise';
const pathTo = (relative: string) => `http://localhost:8983/solr/dash/${relative}`;
diff --git a/src/server/database.ts b/src/server/database.ts
index 2372cbcf2..b7aa77f5d 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,11 +1,11 @@
import * as mongodb from 'mongodb';
-import { Transferable } from './Message';
+import * as mongoose from 'mongoose';
import { Opt } from '../fields/Doc';
-import { Utils, emptyFunction } from '../Utils';
+import { emptyFunction, Utils } from '../Utils';
import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
-import { IDatabase, DocumentsCollection } from './IDatabase';
+import { DocumentsCollection, IDatabase } from './IDatabase';
import { MemoryDatabase } from './MemoryDatabase';
-import * as mongoose from 'mongoose';
+import { Transferable } from './Message';
import { Upload } from './SharedMediaTypes';
export namespace Database {
diff --git a/src/server/downsize.ts b/src/server/downsize.ts
index 5cd709fa3..382994e2d 100644
--- a/src/server/downsize.ts
+++ b/src/server/downsize.ts
@@ -1,5 +1,5 @@
-import * as sharp from 'sharp';
import * as fs from 'fs';
+import * as sharp from 'sharp';
const folder = "./src/server/public/files/";
const pngTypes = ["png", "PNG"];
diff --git a/src/server/index.ts b/src/server/index.ts
index 9af4b00bc..9185e3c5e 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,29 +1,29 @@
require('dotenv').config();
-import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
+import { yellow } from "colors";
import * as mobileDetect from 'mobile-detect';
import * as path from 'path';
-import { Database } from './database';
-import { DashUploadUtils } from './DashUploadUtils';
-import RouteSubscriber from './RouteSubscriber';
-import initializeServer, { resolvedPorts } from './server_Initialization';
-import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager';
import * as qs from 'query-string';
-import UtilManager from './ApiManagers/UtilManager';
-import { SearchManager } from './ApiManagers/SearchManager';
-import UserManager from './ApiManagers/UserManager';
-import DownloadManager from './ApiManagers/DownloadManager';
-import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
-import DeleteManager from "./ApiManagers/DeleteManager";
-import PDFManager from "./ApiManagers/PDFManager";
-import UploadManager from "./ApiManagers/UploadManager";
import { log_execution } from "./ActionUtilities";
+import DeleteManager from "./ApiManagers/DeleteManager";
+import DownloadManager from './ApiManagers/DownloadManager';
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
-import HypothesisManager from "./ApiManagers/HypothesisManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import { Logger } from "./ProcessFactory";
-import { yellow } from "colors";
+import HypothesisManager from "./ApiManagers/HypothesisManager";
+import PDFManager from "./ApiManagers/PDFManager";
+import { SearchManager } from './ApiManagers/SearchManager';
import SessionManager from "./ApiManagers/SessionManager";
+import UploadManager from "./ApiManagers/UploadManager";
+import UserManager from './ApiManagers/UserManager';
+import UtilManager from './ApiManagers/UtilManager';
+import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
+import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
+import { DashUploadUtils } from './DashUploadUtils';
+import { Database } from './database';
+import { Logger } from "./ProcessFactory";
+import RouteManager, { Method, PublicHandler } from './RouteManager';
+import RouteSubscriber from './RouteSubscriber';
+import initializeServer, { resolvedPorts } from './server_Initialization';
export const AdminPriviliges: Map<string, boolean> = new Map();
export const onWindows = process.platform === "win32";
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 744d4547b..e40f2b8e5 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -1,31 +1,31 @@
+import * as bodyParser from 'body-parser';
+import { blue, yellow } from 'colors';
+import * as cookieParser from 'cookie-parser';
+import * as cors from "cors";
import * as express from 'express';
-import * as expressValidator from 'express-validator';
import * as session from 'express-session';
+import * as expressValidator from 'express-validator';
+import * as fs from 'fs';
+import { Server as HttpServer } from "http";
+import { createServer, Server as HttpsServer } from "https";
import * as passport from 'passport';
-import * as bodyParser from 'body-parser';
-import * as cookieParser from 'cookie-parser';
-import expressFlash = require('express-flash');
-import flash = require('connect-flash');
-import { Database } from './database';
-import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
-const MongoStore = require('connect-mongo')(session);
-import RouteManager from './RouteManager';
-import { WebSocket } from './websocket';
+import * as request from 'request';
import * as webpack from 'webpack';
-const config = require('../../webpack.config');
-const compiler = webpack(config);
import * as wdm from 'webpack-dev-middleware';
import * as whm from 'webpack-hot-middleware';
-import * as fs from 'fs';
-import * as request from 'request';
-import RouteSubscriber from './RouteSubscriber';
import { publicDirectory } from '.';
import { logPort } from './ActionUtilities';
-import { blue, yellow } from 'colors';
-import * as cors from "cors";
-import { createServer, Server as HttpsServer } from "https";
-import { Server as HttpServer } from "http";
import { SSL } from './apis/google/CredentialsLoader';
+import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
+import { Database } from './database';
+import RouteManager from './RouteManager';
+import RouteSubscriber from './RouteSubscriber';
+import { WebSocket } from './websocket';
+import expressFlash = require('express-flash');
+import flash = require('connect-flash');
+const MongoStore = require('connect-mongo')(session);
+const config = require('../../webpack.config');
+const compiler = webpack(config);
/* RouteSetter is a wrapper around the server that prevents the server
from being exposed. */
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index f63a35e43..63cfa41f0 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,22 +1,22 @@
-import * as fs from 'fs';
-import { logPort } from './ActionUtilities';
+import * as express from "express";
+import { blue, green } from "colors";
+import { createServer, Server } from "https";
+import { networkInterfaces } from "os";
+import * as sio from 'socket.io';
+import { Socket } from "socket.io";
+import executeImport from "../scraping/buxton/final/BuxtonImporter";
import { Utils } from "../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "./Message";
+import { logPort } from './ActionUtilities';
+import { timeMap } from "./ApiManagers/UserManager";
+import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
+import YoutubeApi from "./apis/youtube/youtubeApiSample";
import { Client } from "./Client";
-import { Socket } from "socket.io";
import { Database } from "./database";
-import { Search } from "./Search";
-import * as sio from 'socket.io';
-import YoutubeApi from "./apis/youtube/youtubeApiSample";
-import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
-import { timeMap } from "./ApiManagers/UserManager";
-import { green } from "colors";
-import { networkInterfaces } from "os";
-import executeImport from "../scraping/buxton/final/BuxtonImporter";
import { DocumentsCollection } from "./IDatabase";
-import { createServer, Server } from "https";
-import * as express from "express";
+import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message";
+import { Search } from "./Search";
import { resolvedPorts } from './server_Initialization';
+import { Opt } from "../fields/Doc";
export namespace WebSocket {
@@ -32,7 +32,7 @@ export namespace WebSocket {
if (socketPort) {
resolvedPorts.socket = Number(socketPort);
}
- let socketEndpoint: Server;
+ let socketEndpoint: Opt<Server>;
await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
io = sio(socketEndpoint!, SSL.Credentials as any);
} else {
@@ -187,7 +187,7 @@ export namespace WebSocket {
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
- console.log(green(`user ${userEmail} has connected to the web socket at: ${datetime}`));
+ console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`));
socketMap.set(socket, userEmail);
}
@@ -209,10 +209,12 @@ export namespace WebSocket {
}
function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
+ process.stdout.write(`.`);
Database.Instance.getDocument(id, callback);
}
function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) {
+ process.stdout.write(`${ids.length}…`);
Database.Instance.getDocuments(ids, callback);
}
@@ -228,7 +230,8 @@ export namespace WebSocket {
"script": ["_t", value => value.script.originalScript],
"RichTextField": ["_t", value => value.Text],
"date": ["_d", value => new Date(value.date).toISOString()],
- "proxy": ["_i", "fieldId"],
+ // "proxy": ["_i", "fieldId"],
+ // "proxy": ["", "fieldId"],
"list": ["_l", list => {
const results = [];
for (const value of list.fields) {
@@ -242,25 +245,27 @@ export namespace WebSocket {
};
function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
+
if (val === null || val === undefined) {
return;
}
const type = val.__type || typeof val;
+
let suffix = suffixMap[type];
if (!suffix) {
return;
}
-
if (Array.isArray(suffix)) {
const accessor = suffix[1];
if (typeof accessor === "function") {
val = accessor(val);
} else {
val = val[accessor];
+
}
suffix = suffix[0];
- }
+ }
return { suffix, value: val };
}
@@ -282,7 +287,7 @@ export namespace WebSocket {
dynfield = true;
const val = docfield[key];
key = key.substring(7);
- Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null });
+ Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; });
const term = ToSearchTerm(val);
if (term !== undefined) {
const { suffix, value } = term;
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 728dd51d3..068ac2159 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -9,6 +9,10 @@ declare module 'bezier-curve';
declare module 'fit-curve';
declare module 'react-audio-waveform';
+declare module 'reveal';
+declare module 'react-reveal';
+declare module 'react-reveal/makeCarousel';
+declare module 'react-resizable-rotatable-draggable';
declare module '@react-pdf/renderer' {
import * as React from 'react';