aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/MainView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/MainView.tsx')
-rw-r--r--src/client/views/MainView.tsx167
1 files changed, 124 insertions, 43 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4d5dfc99e..6bbe09974 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,12 +1,14 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faHireAHelper, faBuffer } from '@fortawesome/free-brands-svg-icons';
+import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
+import * as ReactDOM from 'react-dom';
import Measure from 'react-measure';
+import * as rp from 'request-promise';
import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
@@ -14,16 +16,20 @@ import { listSpec } from '../../fields/Schema';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils, simulateMouseClick } from '../../Utils';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
+import { Networking } from '../Network';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DocumentManager } from '../util/DocumentManager';
import GroupManager from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
+import { Hypothesis } from '../util/HypothesisUtils';
+import { LinkManager } from '../util/LinkManager';
import { Scripting } from '../util/Scripting';
+import { SearchUtil } from '../util/SearchUtil';
import { SelectionManager } from '../util/SelectionManager';
import SettingsManager from '../util/SettingsManager';
import SharingManager from '../util/SharingManager';
@@ -41,34 +47,28 @@ import { ContextMenu } from './ContextMenu';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import GestureOverlay from './GestureOverlay';
-import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
+import { ANTIMODEMENU_HEIGHT, SEARCH_PANEL_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 { WebBox } from './nodes/WebBox';
import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
-import { Hypothesis } from '../util/HypothesisUtils';
-import { undoBatch } from '../util/UndoManager';
-import { WebBox } from './nodes/WebBox';
-import * as ReactDOM from 'react-dom';
import { SearchBox } from './search/SearchBox';
@observer
export class MainView extends React.Component {
public static Instance: MainView;
private _buttonBarHeight = 36;
- private _flyoutSizeOnDown = 0;
private _urlState: HistoryUtil.DocUrl;
private _docBtnRef = React.createRef<HTMLDivElement>();
private _mainViewRef = React.createRef<HTMLDivElement>();
@@ -143,6 +143,7 @@ export class MainView extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
MainView.Instance = this;
+ this.sidebarContent.proto = undefined;
this._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
// causes errors to be generated when modifying an observable outside of an action
@@ -164,7 +165,7 @@ export class MainView extends React.Component {
}
}
- library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt,
+ library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
@@ -180,7 +181,8 @@ export class MainView extends React.Component {
fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper,
fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle,
fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
- fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer);
+ fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
+ fa.faWindowMinimize, fa.faWindowRestore);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -199,7 +201,7 @@ export class MainView extends React.Component {
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") {
+ if (thing.className.toString() === "collectionSchemaView-searchContainer" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "collectionSchema-header-menuOptions" || thing.className.toString() === "altcollectionTimeView-treeView") {
check = true;
}
});
@@ -251,6 +253,8 @@ export class MainView extends React.Component {
@action
createNewWorkspace = async (id?: string) => {
+ const myCatalog = Doc.UserDoc().myCatalog as Doc;
+ const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true);
const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
const workspaceCount = DocListCast(workspaces.data).length + 1;
const freeformOptions: DocumentOptions = {
@@ -258,11 +262,13 @@ export class MainView extends React.Component {
y: 400,
_width: this._panelWidth * .7 - this.propertiesWidth() * 0.7,
_height: this._panelHeight,
- title: "Collection " + workspaceCount,
+ title: "Untitled Collection",
};
const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
-
+ const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [myCatalog] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+ Doc.AddDocToList(myCatalog, "data", freeformDoc);
+ Doc.AddDocToList(myCatalog, "data", presentation);
+ Doc.UserDoc().activePresentation = presentation;
const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
const copyWorkspace = ScriptField.MakeScript(`copyWorkspace()`);
@@ -307,11 +313,7 @@ export class MainView extends React.Component {
DocServer.Control.makeEditable();
}
}
- // 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["sidebar-sharing"], Doc);
- col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col);
- }, 100);
+
return true;
}
@@ -332,7 +334,7 @@ export class MainView extends React.Component {
getPHeight = () => this._panelHeight;
getContentsHeight = () => this._panelHeight - this._buttonBarHeight;
- defaultBackgroundColors = (doc: Opt<Doc>) => {
+ defaultBackgroundColors = (doc: Opt<Doc>, renderDepth: number) => {
if (this.panelContent === doc?.title) return "lightgrey";
if (doc?.type === DocumentType.COL) {
@@ -342,7 +344,7 @@ export class MainView extends React.Component {
|| doc.title === "Advanced Item Prototypes" || doc.title === "all Creators") {
return "lightgrey";
}
- return StrCast(Doc.UserDoc().defaultColor);
+ return StrCast(renderDepth > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground);
}
if (this.darkScheme) {
switch (doc?.type) {
@@ -402,7 +404,7 @@ export class MainView extends React.Component {
TraceMobx();
const mainContainer = this.mainContainer;
const width = this.flyoutWidth + this.propertiesWidth();
- return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - 32px)` }}>
+ return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - ${SEARCH_PANEL_HEIGHT})` }}>
{!mainContainer ? (null) : this.mainDocView}
</div>;
}
@@ -419,7 +421,7 @@ export class MainView extends React.Component {
onFlyoutPointerDown = (e: React.PointerEvent) => {
if (this._flyoutTranslate) {
setupMoveUpEvents(this, e, action((e: PointerEvent) => {
- this.flyoutWidth = Math.max(e.clientX, 0);
+ this.flyoutWidth = Math.max(e.clientX - 58, 0);
if (this.flyoutWidth < 5) {
this.panelContent = "none";
this._lastButton && (this._lastButton.color = "white");
@@ -439,15 +441,13 @@ export class MainView extends React.Component {
doc.dockingConfig ? this.openWorkspace(doc) :
CollectionDockingView.AddRightSplit(doc, libraryPath);
}
- 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, -this._buttonBarHeight);
+ sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0) - Number(SEARCH_PANEL_HEIGHT.replace("px", "")), 1);
+ mainContainerXf = () => this.sidebarScreenToLocal().translate(-58, 0);
- @computed get closePosition() { return 55 + this.flyoutWidth; }
@computed get flyout() {
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" }}>
+ <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${SEARCH_PANEL_HEIGHT})`, width: "100%", overflow: "visible" }}>
{/* {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close"
onPointerDown={this.closeFlyout}>
<FontAwesomeIcon icon="times" color="black" size="lg" />
@@ -531,22 +531,24 @@ export class MainView extends React.Component {
_lastButton: Doc | undefined;
@action
- selectMenu = (button: Doc, str: string) => {
+ selectMenu = (button: Doc) => {
+ const title = StrCast(Doc.GetProto(button).title);
this._lastButton && (this._lastButton.color = "white");
this._lastButton && (this._lastButton._backgroundColor = "");
- if (this.panelContent === str && this.flyoutWidth !== 0) {
+ if (this.panelContent === title && 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;
+ switch (this.panelContent = title) {
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;
+ case "Catalog": SearchBox.Instance._searchFullDB = "My Stuff";
+ SearchBox.Instance.newsearchstring = "";
+ SearchBox.Instance.enter(undefined);
+ break;
+ // panelDoc = Doc.UserDoc()["sidebar-catalog"] as Doc ?? undefined; break;
+ default:
+ panelDoc = button.target as any; break;
}
this.sidebarContent.proto = panelDoc;
if (panelDoc) {
@@ -610,7 +612,6 @@ export class MainView extends React.Component {
</div>
</div>
{this.dockingContent}
- <MainViewNotifs />
{this.showProperties ? (null) :
<div className="mainView-propertiesDragger" title="Properties View Dragger" onPointerDown={this.onPropertiesPointerDown}
style={{ right: rightFlyout, top: "50%" }}>
@@ -654,7 +655,11 @@ export class MainView extends React.Component {
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);
+ addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => {
+ const ret = flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc);
+ ret && (doc._stayInCollection = undefined);
+ return ret;
+ }, true)
remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
@@ -820,7 +825,6 @@ export class MainView extends React.Component {
{this.search}
<CollectionMenu />
<FormatShapePane />
- <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
@@ -895,8 +899,83 @@ export class MainView extends React.Component {
document.addEventListener("editSuccess", onSuccess);
});
}
+
+ importDocument = () => {
+ const sidebar = Cast(Doc.UserDoc()["sidebar-import-documents"], Doc, null);
+ const sidebarDocView = DocumentManager.Instance.getDocumentView(sidebar);
+ const input = document.createElement("input");
+ input.type = "file";
+ input.multiple = true;
+ input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
+ input.onchange = async _e => {
+ const upload = Utils.prepend("/uploadDoc");
+ const formData = new FormData();
+ const file = input.files && input.files[0];
+ if (file && file.type === 'application/zip') {
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json !== "error") {
+ const doc = await DocServer.GetRefField(json);
+ if (doc instanceof Doc && sidebarDocView) {
+ sidebarDocView.props.addDocument?.(doc);
+ setTimeout(() => {
+ SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
+ docs.docs.forEach(d => LinkManager.Instance.addLink(d));
+ });
+ }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
+
+ }
+ }
+ } else if (input.files && input.files.length !== 0) {
+ const files = input.files || [];
+ Array.from(files).forEach(async file => {
+ const res = await Networking.UploadFilesToServer(file);
+ res.map(async ({ result }) => {
+ const name = file.name;
+ if (result instanceof Error) {
+ return;
+ }
+ const path = Utils.prepend(result.accessPaths.agnostic.client);
+ let doc: Doc;
+ // Case 1: File is a video
+ if (file.type.includes("video")) {
+ doc = Docs.Create.VideoDocument(path, { _height: 100, title: name });
+ // Case 2: File is a PDF document
+ } else if (file.type === "application/pdf") {
+ doc = Docs.Create.PdfDocument(path, { _height: 100, _fitWidth: true, title: name });
+ // Case 3: File is an image
+ } else if (file.type.includes("image")) {
+ doc = Docs.Create.ImageDocument(path, { _height: 100, title: name });
+ // Case 4: File is an audio document
+ } else {
+ doc = Docs.Create.AudioDocument(path, { title: name });
+ }
+ const res = await rp.get(Utils.prepend("/getUserDocumentId"));
+ if (!res) {
+ throw new Error("No user id returned");
+ }
+ const field = await DocServer.GetRefField(res);
+ let pending: Opt<Doc>;
+ if (field instanceof Doc) {
+ pending = sidebar;
+ }
+ if (pending) {
+ const data = await Cast(pending.data, listSpec(Doc));
+ if (data) data.push(doc);
+ else pending.data = new List([doc]);
+ }
+ });
+ });
+ } else {
+ console.log("No file selected");
+ }
+ };
+ input.click();
+ }
}
-Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
+Scripting.addGlobal(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); });
Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; });
Scripting.addGlobal(function copyWorkspace() {
const copiedWorkspace = Doc.MakeCopy(Cast(Doc.UserDoc().activeWorkspace, Doc, null), true);
@@ -905,3 +984,5 @@ Scripting.addGlobal(function copyWorkspace() {
// bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0);
});
+Scripting.addGlobal(function importDocument() { return MainView.Instance.importDocument(); },
+ "imports files from device directly into the import sidebar");