aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--deploy/assets/endLink.pngbin0 -> 10322 bytes
-rw-r--r--deploy/assets/link.pngbin0 -> 8790 bytes
-rw-r--r--deploy/assets/startLink.pngbin0 -> 10538 bytes
-rw-r--r--deploy/index.html2
-rw-r--r--package-lock.json5
-rw-r--r--package.json6
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.scss26
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx158
-rw-r--r--src/client/documents/Documents.ts75
-rw-r--r--src/client/util/CurrentUserUtils.ts60
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/DragManager.ts50
-rw-r--r--src/client/util/InteractionUtils.tsx15
-rw-r--r--src/client/util/LinkManager.ts19
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/util/SettingsManager.scss2
-rw-r--r--src/client/util/SettingsManager.tsx14
-rw-r--r--src/client/views/AntimodeMenu.tsx24
-rw-r--r--src/client/views/ContextMenuItem.tsx6
-rw-r--r--src/client/views/DocumentButtonBar.tsx181
-rw-r--r--src/client/views/DocumentDecorations.tsx54
-rw-r--r--src/client/views/EditableView.tsx6
-rw-r--r--src/client/views/GestureOverlay.scss2
-rw-r--r--src/client/views/GestureOverlay.tsx119
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/InkingStroke.tsx34
-rw-r--r--src/client/views/MainView.scss4
-rw-r--r--src/client/views/MainView.tsx42
-rw-r--r--src/client/views/MetadataEntryMenu.tsx96
-rw-r--r--src/client/views/OverlayView.scss2
-rw-r--r--src/client/views/OverlayView.tsx4
-rw-r--r--src/client/views/RecommendationsBox.tsx2
-rw-r--r--src/client/views/TemplateMenu.tsx7
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx4
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx23
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx21
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx20
-rw-r--r--src/client/views/collections/CollectionLinearView.scss12
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx58
-rw-r--r--src/client/views/collections/CollectionMenu.scss (renamed from src/client/views/collections/CollectionViewChromes.scss)52
-rw-r--r--src/client/views/collections/CollectionMenu.tsx (renamed from src/client/views/collections/CollectionViewChromes.tsx)226
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx64
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx33
-rw-r--r--src/client/views/collections/CollectionView.tsx42
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx60
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx85
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss64
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx307
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss7
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx326
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx7
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx7
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx6
-rw-r--r--src/client/views/linking/LinkEditor.scss170
-rw-r--r--src/client/views/linking/LinkEditor.tsx152
-rw-r--r--src/client/views/linking/LinkMenu.scss72
-rw-r--r--src/client/views/linking/LinkMenu.tsx42
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx12
-rw-r--r--src/client/views/linking/LinkMenuItem.scss111
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx101
-rw-r--r--src/client/views/nodes/AudioBox.scss1
-rw-r--r--src/client/views/nodes/AudioBox.tsx1
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx6
-rw-r--r--src/client/views/nodes/ColorBox.tsx12
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss17
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx210
-rw-r--r--src/client/views/nodes/DocumentView.tsx145
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FontIconBox.scss1
-rw-r--r--src/client/views/nodes/FontIconBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx48
-rw-r--r--src/client/views/nodes/LabelBox.tsx4
-rw-r--r--src/client/views/nodes/LinkCreatedBox.tsx8
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.scss77
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx65
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx26
-rw-r--r--src/client/views/nodes/SliderBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx69
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss126
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx113
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts3
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx184
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts2
-rw-r--r--src/client/views/pdf/PDFViewer.tsx2
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx4
-rw-r--r--src/fields/documentSchemas.ts9
-rw-r--r--src/server/ApiManagers/HypothesisManager.ts44
-rw-r--r--src/server/ApiManagers/UploadManager.ts3
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts3
-rw-r--r--src/server/index.ts3
-rw-r--r--src/server/websocket.ts9
95 files changed, 2855 insertions, 1463 deletions
diff --git a/deploy/assets/endLink.png b/deploy/assets/endLink.png
new file mode 100644
index 000000000..abadc9550
--- /dev/null
+++ b/deploy/assets/endLink.png
Binary files differ
diff --git a/deploy/assets/link.png b/deploy/assets/link.png
new file mode 100644
index 000000000..2dbcd61ce
--- /dev/null
+++ b/deploy/assets/link.png
Binary files differ
diff --git a/deploy/assets/startLink.png b/deploy/assets/startLink.png
new file mode 100644
index 000000000..8f3825682
--- /dev/null
+++ b/deploy/assets/startLink.png
Binary files differ
diff --git a/deploy/index.html b/deploy/index.html
index 29a3f15cd..82e8c53a3 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -8,7 +8,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
</head>
-<body>
+<body style="display:flex">
<!-- <script src="https://hypothes.is/embed.js" async></script> -->
<div id="root" style="position:relative;width:100%;height:100%"></div>
<script src="/bundle.js"></script>
diff --git a/package-lock.json b/package-lock.json
index ad181758c..1b39905cf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14256,6 +14256,11 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "react-loading": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz",
+ "integrity": "sha512-Vdqy79zq+bpeWJqC+xjltUjuGApyoItPgL0vgVfcJHhqwU7bAMKzysfGW/ADu6i0z0JiOCRJjo+IkFNkRNbA3A=="
+ },
"react-measure": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.3.0.tgz",
diff --git a/package.json b/package.json
index 75dc817f5..cb083020f 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,9 @@
},
"scripts": {
"start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
- "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
- "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts",
+ "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --transpile-only -- src/server/index.ts",
+ "oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+ "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
"test": "mocha -r ts-node/register test/**/*.ts",
"tsc": "tsc"
@@ -220,6 +221,7 @@
"react-grid-layout": "^0.18.3",
"react-image-lightbox-with-rotate": "^5.1.1",
"react-jsx-parser": "^1.25.1",
+ "react-loading": "^2.0.3",
"react-measure": "^2.2.4",
"react-resizable": "^1.10.1",
"react-select": "^3.1.0",
diff --git a/src/client/apis/HypothesisAuthenticationManager.scss b/src/client/apis/HypothesisAuthenticationManager.scss
new file mode 100644
index 000000000..bd30dd94f
--- /dev/null
+++ b/src/client/apis/HypothesisAuthenticationManager.scss
@@ -0,0 +1,26 @@
+.authorize-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .paste-target {
+ padding: 5px;
+ width: 100%;
+ }
+
+ .avatar {
+ border-radius: 50%;
+ }
+
+ .welcome {
+ font-style: italic;
+ margin-top: 15px;
+ }
+
+ .disconnect {
+ font-size: 10px;
+ margin-top: 20px;
+ color: red;
+ cursor: grab;
+ }
+} \ No newline at end of file
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
new file mode 100644
index 000000000..a7fcf86a4
--- /dev/null
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -0,0 +1,158 @@
+import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { Opt } from "../../fields/Doc";
+import { Networking } from "../Network";
+import "./HypothesisAuthenticationManager.scss";
+import { Scripting } from "../util/Scripting";
+
+const prompt = "Paste authorization code here...";
+
+@observer
+export default class HypothesisAuthenticationManager extends React.Component<{}> {
+ public static Instance: HypothesisAuthenticationManager;
+ private authenticationLink: Opt<string> = undefined;
+ @observable private openState = false;
+ @observable private authenticationCode: Opt<string> = undefined;
+ @observable private showPasteTargetState = false;
+ @observable private success: Opt<boolean> = undefined;
+ @observable private displayLauncher = true;
+ @observable private credentials: string;
+ private disposer: Opt<IReactionDisposer>;
+
+ private set isOpen(value: boolean) {
+ runInAction(() => this.openState = value);
+ }
+
+ private set shouldShowPasteTarget(value: boolean) {
+ runInAction(() => this.showPasteTargetState = value);
+ }
+
+ public cancel() {
+ this.openState && this.resetState(0, 0);
+ }
+
+ public fetchAccessToken = async (displayIfFound = false) => {
+ const response: any = await Networking.FetchFromServer("/readHypothesisAccessToken");
+ // if this is an authentication url, activate the UI to register the new access token
+ if (!response) { // new RegExp(AuthenticationUrl).test(response)) {
+ this.isOpen = true;
+ this.authenticationLink = response;
+ return new Promise<string>(async resolve => {
+ this.disposer?.();
+ this.disposer = reaction(
+ () => this.authenticationCode,
+ async authenticationCode => {
+ if (authenticationCode) {
+ this.disposer?.();
+ Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode });
+ runInAction(() => {
+ this.success = true;
+ this.credentials = response;
+ });
+ this.resetState();
+ resolve(authenticationCode);
+ }
+ }
+ );
+ });
+ }
+
+ if (displayIfFound) {
+ runInAction(() => {
+ this.success = true;
+ this.credentials = response;
+ });
+ this.resetState(-1, -1);
+ this.isOpen = true;
+ }
+ return response.access_token;
+ }
+
+ resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
+ if (!visibleForMS && !fadesOutInMS) {
+ runInAction(() => {
+ this.isOpen = false;
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = "";
+ this.shouldShowPasteTarget = false;
+ this.authenticationCode = undefined;
+ });
+ return;
+ }
+ this.authenticationCode = undefined;
+ this.displayLauncher = false;
+ this.shouldShowPasteTarget = false;
+ if (visibleForMS > 0 && fadesOutInMS > 0) {
+ setTimeout(action(() => {
+ this.isOpen = false;
+ setTimeout(action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = "";
+ }), fadesOutInMS);
+ }), visibleForMS);
+ }
+ });
+
+ constructor(props: {}) {
+ super(props);
+ HypothesisAuthenticationManager.Instance = this;
+ }
+
+ private get renderPrompt() {
+ return (
+ <div className={'authorize-container'}>
+
+ {this.displayLauncher ? <button
+ className={"dispatch"}
+ onClick={() => {
+ this.shouldShowPasteTarget = true;
+ }}
+ style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
+ >Authorize a Hypothesis account...</button> : (null)}
+ {this.showPasteTargetState ? <input
+ className={'paste-target'}
+ onChange={action(e => this.authenticationCode = e.currentTarget.value)}
+ placeholder={prompt}
+ /> : (null)}
+ {this.credentials ?
+ <>
+ <span
+ className={'welcome'}
+ >Welcome to Dash, {this.credentials}
+ </span>
+ <div
+ className={'disconnect'}
+ onClick={async () => {
+ await Networking.FetchFromServer("/revokeHypothesisAccessToken");
+ this.resetState(0, 0);
+ }}
+ >Disconnect Account</div>
+ </> : (null)}
+ </div>
+ );
+ }
+
+ private get dialogueBoxStyle() {
+ const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
+ return { borderColor, transition: "0.2s borderColor ease" };
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ isDisplayed={this.openState}
+ interactive={true}
+ contents={this.renderPrompt}
+ overlayDisplayedOpacity={0.9}
+ dialogueBoxStyle={this.dialogueBoxStyle}
+ />
+ );
+ }
+
+}
+
+Scripting.addGlobal("HypothesisAuthenticationManager", HypothesisAuthenticationManager); \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f81c25bab..b57f4c6c7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -94,6 +94,7 @@ export interface DocumentOptions {
label?: string; // short form of title for use as an icon label
style?: string;
page?: number;
+ description?: string; // added for links
_viewScale?: number;
isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
forceActive?: boolean;
@@ -101,6 +102,8 @@ export interface DocumentOptions {
childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
childLayoutString?: string; // template string for collection to use to render its children
hideFilterView?: boolean; // whether to hide the filter popout on collections
+ hideLinkButton?: boolean; // whether the blue link counter button should be hidden
+ hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
_columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
isTemplateForField?: string; // the field key for which the containing document is a rendering template
isTemplateDoc?: boolean;
@@ -122,7 +125,7 @@ export interface DocumentOptions {
isBackground?: boolean;
isLinkButton?: boolean;
_columnWidth?: number;
- _fontSize?: number;
+ _fontSize?: string;
_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 +138,8 @@ export interface DocumentOptions {
dontRegisterChildViews?: boolean;
lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox.
"onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
+ "onChildDoubleClick-rawScript"?: string; // onChildDoubleClick script in raw text form
+ "onChildClick-rawScript"?: string; // on ChildClick script in raw text form
"onClick-rawScript"?: string; // onClick script in raw text form
"onCheckedClick-rawScript"?: string; // onChecked script in raw text form
"onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions
@@ -259,7 +264,7 @@ export namespace Docs {
}],
[DocumentType.LINK, {
layout: { view: LinkBox, dataField: defaultDataKey },
- options: { _height: 150 }
+ options: { _height: 150, description: "" }
}],
[DocumentType.LINKDB, {
data: new List<Doc>(),
@@ -670,12 +675,12 @@ export namespace Docs {
I.type = DocumentType.INK;
I.layout = InkingStroke.LayoutString("data");
I.color = color;
- I.strokeWidth = strokeWidth;
- I.strokeBezier = strokeBezier;
I.fillColor = fillColor;
- I.arrowStart = arrowStart;
- I.arrowEnd = arrowEnd;
- I.dash = dash;
+ I.strokeWidth = Number(strokeWidth);
+ I.strokeBezier = strokeBezier;
+ I.strokeStartMarker = arrowStart;
+ I.strokeEndMarker = arrowEnd;
+ I.strokeDash = dash;
I.tool = tool;
I.title = "ink";
I.x = options.x;
@@ -684,14 +689,9 @@ export namespace Docs {
I._width = options._width;
I._height = options._height;
I.author = Doc.CurrentUserEmail;
+ I.rotation = 0;
I.data = new InkField(points);
return I;
- // return I;
- // const doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
- // doc.color = color;
- // doc.strokeWidth = strokeWidth;
- // doc.tool = tool;
- // return doc;
}
export function PdfDocument(url: string, options: DocumentOptions = {}) {
@@ -781,7 +781,7 @@ export namespace Docs {
export function FontIconDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
+ return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { hideLinkButton: true, ...(options || {}) });
}
export function PresElementBoxDocument(options?: DocumentOptions) {
@@ -829,6 +829,47 @@ export namespace Docs {
}
export namespace DocUtils {
+ export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField) {
+ const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+
+ const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [key, value, modifiers] = docFilters.slice(i, i + 3);
+ if (!filterFacets[key]) {
+ filterFacets[key] = {};
+ }
+ filterFacets[key][value] = modifiers;
+ }
+
+ const filteredDocs = docFilters.length ? childDocs.filter(d => {
+ for (const facetKey of Object.keys(filterFacets)) {
+ const facet = filterFacets[facetKey];
+ const satisfiesFacet = Object.keys(facet).some(value => {
+ if (facet[value] === "match") {
+ return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
+ }
+ return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
+ });
+ if (!satisfiesFacet) {
+ return false;
+ }
+ }
+ return true;
+ }) : childDocs;
+ const rangeFilteredDocs = filteredDocs.filter(d => {
+ for (let i = 0; i < docRangeFilters.length; i += 3) {
+ const key = docRangeFilters[i];
+ const min = Number(docRangeFilters[i + 1]);
+ const max = Number(docRangeFilters[i + 2]);
+ const val = Cast(d[key], "number", null);
+ if (val !== undefined && (val < min || val > max)) {
+ return false;
+ }
+ }
+ return true;
+ });
+ return rangeFilteredDocs;
+ }
export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, "");
@@ -867,12 +908,14 @@ export namespace DocUtils {
DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
}
- export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
+ export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
const sv = DocumentManager.Instance.getDocumentView(source.doc);
if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
if (target.doc === Doc.UserDoc()) return undefined;
- const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id);
+ const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
+ linkDoc.linkDisplay = true;
+ linkDoc.hidden = true;
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');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 4276e04e4..e0cb90b46 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -211,7 +211,7 @@ export class CurrentUserUtils {
details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
- const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 };
+ const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" };
const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" };
const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
@@ -402,20 +402,20 @@ export class CurrentUserUtils {
this.setupActiveMobileMenu(doc);
}
return [
- { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
{ title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
{ title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
- { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
- { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
- { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
+ { title: "Drag a cat image", label: "Image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
+ { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
+ { title: "Drag a screengrabber", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
+ // { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
{ title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
- { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
+ { title: "Drag a button", label: "Button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
{ title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
{ title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
{ title: "Drag a scripting box", label: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
- { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
+ // { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
{ title: "Drag a mobile view", label: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
- { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
+ // { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
// { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
@@ -423,7 +423,6 @@ export class CurrentUserUtils {
// { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
{ title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
{ title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
- { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
];
}
@@ -524,13 +523,13 @@ export class CurrentUserUtils {
// Sets up the title of the button
static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
...opts,
- dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
+ dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
}) as any as Doc
// Sets up the description of the button
static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
...opts,
- dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
+ dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
}) as any as Doc
@@ -597,7 +596,7 @@ export class CurrentUserUtils {
_width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
})) as any as Doc;
doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 35, _height: 25, title: "Tools", _fontSize: 10,
+ _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('getAlias(this.dragFactory, true)'),
@@ -662,7 +661,7 @@ export class CurrentUserUtils {
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: 10, targetDropAction: "same",
+ _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('getAlias(this.dragFactory, true)'),
@@ -680,7 +679,7 @@ export class CurrentUserUtils {
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: 10,
+ _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]),
@@ -776,17 +775,17 @@ export class CurrentUserUtils {
static setupClickEditorTemplates(doc: Doc) {
if (doc["clickFuncs-child"] === undefined) {
+ // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
- "docCast(thisContainer.target).then((target) => {" +
- " target && docCast(this.source).then((source) => { " +
- " target.proto.data = new List([source || this]); " +
- " }); " +
- "})",
- { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" });
+ "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ",
+ { thisContainer: Doc.name }), {
+ title: "Click to open in target", _width: 300, _height: 200,
+ targetScriptKey: "onChildClick",
+ });
const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
"openOnRight(self.doubleClickView)",
- { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
+ {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
}
@@ -798,14 +797,22 @@ export class CurrentUserUtils {
title: "onClick", "onClick-rawScript": "console.log('click')",
isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200
}, "onClick");
+ const onChildClick = Docs.Create.ScriptingDocument(undefined, {
+ title: "onChildClick", "onChildClick-rawScript": "console.log('child click')",
+ isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200
+ }, "onChildClick");
const onDoubleClick = Docs.Create.ScriptingDocument(undefined, {
title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')",
isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200
}, "onDoubleClick");
+ const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, {
+ title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')",
+ isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200
+ }, "onChildDoubleClick");
const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200
}, "onCheckedClick");
- doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" });
+ doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" });
}
PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
@@ -819,11 +826,12 @@ export class CurrentUserUtils {
doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
doc.activeInkWidth = StrCast(doc.activeInkWidth, "1");
doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
- doc.activeFillColor = StrCast(doc.activeFillColor, "none");
- doc.activeArrowStart = StrCast(doc.activeArrowStart, "none");
- doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "none");
+ doc.activeFillColor = StrCast(doc.activeFillColor, "");
+ doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
+ doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
doc.activeDash = StrCast(doc.activeDash, "0");
- doc.fontSize = NumCast(doc.fontSize, 12);
+ doc.fontSize = StrCast(doc.fontSize, "12pt");
+ doc.fontFamily = StrCast(doc.fontFamily, "Arial");
doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 1fa5faeb3..b66e7fdc4 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -146,7 +146,7 @@ export class DocumentManager {
};
const docView = getFirstDocView(targetDoc, originatingDoc);
let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
- if (annotatedDoc && !linkDoc?.isPushpin) {
+ if (annotatedDoc && !targetDoc?.isPushpin) {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
@@ -156,7 +156,7 @@ export class DocumentManager {
}
}
if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
- if (linkDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden;
+ if (originatingDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden;
else {
docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2ceafff30..5f34509a1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -323,7 +323,7 @@ export namespace DragManager {
dragLabel = document.createElement("div");
dragLabel.className = "dragManager-dragLabel";
dragLabel.style.zIndex = "100001";
- dragLabel.style.fontSize = "10";
+ dragLabel.style.fontSize = "10pt";
dragLabel.style.position = "absolute";
// dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
dragDiv.appendChild(dragLabel);
@@ -427,6 +427,54 @@ export namespace DragManager {
}, dragData.droppedDocuments);
}
+ const target = document.elementFromPoint(e.x, e.y);
+
+ const complete = new DragCompleteEvent(false, dragData);
+
+ if (target) {
+ target.dispatchEvent(
+ new CustomEvent<React.DragEvent>("dashDragging", {
+ bubbles: true,
+ detail: {
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey,
+ clientX: e.clientX,
+ clientY: e.clientY,
+ dataTransfer: new DataTransfer,
+ button: e.button,
+ buttons: e.buttons,
+ getModifierState: e.getModifierState,
+ movementX: e.movementX,
+ movementY: e.movementY,
+ pageX: e.pageX,
+ pageY: e.pageY,
+ relatedTarget: e.relatedTarget,
+ screenX: e.screenX,
+ screenY: e.screenY,
+ detail: e.detail,
+ view: e.view ? e.view : new Window,
+ nativeEvent: new DragEvent("dashDragging"),
+ currentTarget: target,
+ target: target,
+ bubbles: true,
+ cancelable: true,
+ defaultPrevented: true,
+ eventPhase: e.eventPhase,
+ isTrusted: true,
+ preventDefault: e.preventDefault,
+ isDefaultPrevented: () => true,
+ stopPropagation: e.stopPropagation,
+ isPropagationStopped: () => true,
+ persist: emptyFunction,
+ timeStamp: e.timeStamp,
+ type: "dashDragging"
+ }
+ })
+ );
+ }
+
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
const moveX = thisX - lastX;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 02b444cd3..07adbb8b1 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -94,6 +94,7 @@ export namespace InteractionUtils {
export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
+
let pts: { X: number; Y: number; }[] = [];
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
@@ -119,15 +120,15 @@ export namespace InteractionUtils {
const dashArray = String(Number(width) * Number(dash));
const defGuid = Utils.GenerateGuid();
const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
- return (<svg fill={fill === "none" ? color : fill}> {/* setting the svg fill sets the arrowhead fill */}
+ return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */}
{nodefs ? (null) : <defs>
{arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible">
<circle r={1} fill="context-stroke" />
</marker>}
- {arrowStart !== "arrowHead" && arrowEnd !== "arrowHead" ? (null) : <marker id={`arrowHead${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
+ {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowStart${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
<polygon points={`${arrowDim} ${-Math.max(1, arrowDim / 2)}, ${arrowDim} ${Math.max(1, arrowDim / 2)}, -1 0`} />
</marker>}
- {arrowStart !== "arrowEnd" && arrowEnd !== "arrowEnd" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
+ {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
<polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
</marker>}
</defs>}
@@ -136,7 +137,7 @@ export namespace InteractionUtils {
points={strpts}
style={{
filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
- fill,
+ fill: fill ? fill : "transparent",
opacity: strokeWidth !== width ? 0.5 : undefined,
pointerEvents: pevents as any,
stroke: color ?? "rgb(0, 0, 0)",
@@ -145,8 +146,8 @@ export namespace InteractionUtils {
strokeLinecap: "round",
strokeDasharray: dashArray
}}
- markerStart={`url(#${arrowStart + defGuid})`}
- markerEnd={`url(#${arrowEnd + defGuid})`}
+ markerStart={`url(#${arrowStart + "Start" + defGuid})`}
+ markerEnd={`url(#${arrowEnd + "End" + defGuid})`}
/>
</svg>);
@@ -155,7 +156,7 @@ export namespace InteractionUtils {
// export function makeArrow() {
// return (
// InkOptionsMenu.Instance.getColors().map(color => {
- // const id1 = "arrowHeadTest" + color;
+ // const id1 = "arrowStartTest" + color;
// console.log(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} />
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 749fabfcc..974744344 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,8 +1,9 @@
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
import { Scripting } from "./Scripting";
+import { undoBatch } from "./UndoManager";
/*
* link doc:
@@ -23,6 +24,10 @@ import { Scripting } from "./Scripting";
export class LinkManager {
private static _instance: LinkManager;
+
+
+ public static currentLink: Opt<Doc>;
+
public static get Instance(): LinkManager {
return this._instance || (this._instance = new this());
}
@@ -48,6 +53,7 @@ export class LinkManager {
return false;
}
+ @undoBatch
public deleteLink(linkDoc: Doc): boolean {
if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
@@ -57,12 +63,17 @@ export class LinkManager {
}
// finds all links that contain the given anchor
- public getAllRelatedLinks(anchor: Doc): Doc[] {
+ public getAllDirectLinks(anchor: Doc): Doc[] {
const related = LinkManager.Instance.getAllLinks().filter(link => {
const protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
const protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
return protomatch1 || protomatch2 || Doc.AreProtosEqual(link, anchor);
});
+ return related;
+ }
+ // finds all links that contain the given anchor
+ public getAllRelatedLinks(anchor: Doc): Doc[] {
+ const related = LinkManager.Instance.getAllDirectLinks(anchor);
DocListCast(anchor[Doc.LayoutFieldKey(anchor) + "-annotations"]).map(anno => {
related.push(...LinkManager.Instance.getAllRelatedLinks(anno));
});
@@ -202,4 +213,6 @@ export class LinkManager {
}
Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
- "creates a link to inputted document", "(doc: any)"); \ No newline at end of file
+ "returns all the links to the document or its annotations", "(doc: any)");
+Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); },
+ "returns all the links directly to the document", "(doc: any)"); \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 024532f90..9a968aeda 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -14,6 +14,7 @@ export namespace SelectionManager {
SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
@action
SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
+
// if doc is not in SelectedDocuments, add it
if (!manager.SelectedDocuments.get(docView)) {
if (!ctrlPressed) {
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index 13c65042c..6d394a38d 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -56,7 +56,7 @@
.settings-type {
display: flex;
flex-direction: column;
- flex-basis: 30%;
+ flex-basis: 45%;
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 9888cce48..981ee698d 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -11,6 +11,8 @@ import { Networking } from "../Network";
import { CurrentUserUtils } from "./CurrentUserUtils";
import { Utils } from "../../Utils";
import { Doc } from "../../fields/Doc";
+import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
+import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
library.add(fa.faWindowClose);
@@ -83,6 +85,14 @@ export default class SettingsManager extends React.Component<{}> {
noviceToggle = (event: any) => {
Doc.UserDoc().noviceMode = !Doc.UserDoc().noviceMode;
}
+ @action
+ googleAuthorize = (event: any) => {
+ GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true);
+ }
+ @action
+ hypothesisAuthorize = (event: any) => {
+ HypothesisAuthenticationManager.Instance.fetchAccessToken(true);
+ }
private get settingsInterface() {
return (
@@ -96,7 +106,9 @@ export default class SettingsManager extends React.Component<{}> {
<div className="settings-body">
<div className="settings-type">
<button onClick={this.onClick} value="password">reset password</button>
- <button onClick={this.noviceToggle} value="data">{`toggle ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
+ <button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} 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={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index cb293dee4..68ccefcb5 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -134,13 +134,35 @@ export default abstract class AntimodeMenu extends React.Component {
protected getElement(buttons: JSX.Element[]) {
return (
<div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
- style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay }}>
+ style={{
+ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay,
+ position: this.Pinned ? "unset" : undefined
+ }}>
<div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />
{buttons}
</div>
);
}
+ protected getElementVert(buttons: JSX.Element[]) {
+ return (
+ <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
+ style={{
+ left: this.Pinned ? undefined : this._left,
+ top: this.Pinned ? 0 : this._top,
+ right: this.Pinned ? 0 : undefined,
+ height: "inherit",
+ width: 200,
+ opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay,
+ position: this.Pinned ? "absolute" : undefined
+ }}>
+ {buttons}
+ </div>
+ );
+ }
+
+
+
protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
return (
<div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 99840047f..68ebd8e14 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -19,6 +19,7 @@ export interface OriginalMenuProps {
export interface SubmenuProps {
description: string;
subitems: ContextMenuProps[];
+ noexpand?: boolean;
icon: IconProp; //maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -96,6 +97,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
<div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px", marginTop }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
+ if (!("noexpand" in this.props)) {
+ return <>
+ {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
+ </>;
+ }
return (
<div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1667b2f65..45f4c7393 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -11,7 +11,6 @@ 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 { UndoManager } from "../util/UndoManager";
import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
import { ParentDocSelector } from './collections/ParentDocumentSelector';
import './collections/ParentDocumentSelector.scss';
@@ -23,6 +22,7 @@ import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { Tooltip } from '@material-ui/core';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -118,18 +118,18 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const targetDoc = this.view0?.props.Document;
const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
- return !targetDoc ? (null) : <div
- title={`${published ? "Push" : "Publish"} to Google Docs`}
- className="documentButtonBar-linker"
- style={{ animation }}
- onClick={async () => {
- await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- !published && runInAction(() => this.isAnimatingPulse = true);
- DocumentButtonBar.hasPushedHack = false;
- targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
- </div>;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
+ <div
+ className="documentButtonBar-linker"
+ style={{ animation }}
+ onClick={async () => {
+ await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
+ !published && runInAction(() => this.isAnimatingPulse = true);
+ DocumentButtonBar.hasPushedHack = false;
+ targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
+ </div></Tooltip>;
}
@computed
@@ -137,82 +137,87 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const targetDoc = this.view0?.props.Document;
const dataDoc = targetDoc && Doc.GetProto(targetDoc);
const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
- return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <div className="documentButtonBar-linker"
- title={(() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
- case UtilityButtonState.OpenRight: return "Open in Right Split";
- case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
- }
- })()}
- style={{ backgroundColor: this.pullColor }}
- onPointerEnter={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;
+
+ 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></>}>
+ <div className="documentButtonBar-linker"
+ style={{ backgroundColor: this.pullColor }}
+ onPointerEnter={action(e => {
+ if (e.altKey) {
+ this.openHover = UtilityButtonState.OpenExternally;
+ } else if (e.shiftKey) {
+ this.openHover = UtilityButtonState.OpenRight;
}
- CollectionDockingView.AddRightSplit(googleDoc);
- } else if (e.altKey) {
- e.preventDefault();
- window.open(googleDocUrl);
- } else {
- this.clearPullColor();
- DocumentButtonBar.hasPulledHack = false;
- targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
- dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
- }
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm"
- 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";
+ })}
+ 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();
+ DocumentButtonBar.hasPulledHack = false;
+ targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
}
- })()}
- />
- </div>;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm"
+ 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></Tooltip>;
}
@computed
get pinButton() {
const targetDoc = this.view0?.props.Document;
const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
- return !targetDoc ? (null) : <div className="documentButtonBar-linker"
- title={Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}
- style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
- onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
- />
- </div>;
+ 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" }}
+ onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
+ />
+ </div></Tooltip>;
}
@computed
get metadataButton() {
const view0 = this.view0;
- return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP}
- content={<MetadataEntryMenu docs={() => this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
- <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
- </div>
- </Flyout>
- </div>;
+ return !view0 ? (null) : <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}>
+ <div className="documentButtonBar-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={<MetadataEntryMenu docs={this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
+ <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
+ </div>
+ </Flyout>
+ </div></Tooltip>;
}
@computed
@@ -253,14 +258,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
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 !view0 ? (null) :
- <div title="Tap: Customize layout. Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
- content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
- <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
- </div>
- </Flyout>
- </div>;
+ <Tooltip title={<><div className="dash-tooltip">Tap: Customize layout. Drag: Create alias</div></>}>
+ <div className="documentButtonBar-linkFlyout" ref={this._dragRef}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
+ content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+ <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
+ </div>
+ </Flyout>
+ </div></Tooltip>;
}
render() {
@@ -271,8 +277,11 @@ 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} />
+ <DocumentLinksButton 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">
{this.templateButton}
</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a45ef8862..376b1d46b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -17,14 +17,12 @@ import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { DocumentView } from "./nodes/DocumentView";
import React = require("react");
-import { Id, Copy, Update } from '../../fields/FieldSymbols';
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 { update } from 'serializr';
-import { Transform } from "../util/Transform";
+import { Tooltip } from '@material-ui/core';
library.add(faCaretUp);
library.add(faObjectGroup);
@@ -83,6 +81,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.renderDepth === 0 ||
documentView.props.treeViewDoc ||
+ !documentView.ContentDiv ||
Doc.AreProtosEqual(documentView.props.Document, Doc.UserDoc())) {
return bounds;
}
@@ -90,7 +89,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
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) {
- const docuBox = documentView.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
+ const docuBox = documentView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
if (docuBox.length) {
const rect = docuBox[0].getBoundingClientRect();
sptX = rect.left;
@@ -152,8 +151,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 => !c.className.includes("collectionViewChrome"));
- if (next?.className.includes("documentView-node")) break;
+ const next = Array.from(child.children).find(c => typeof (c.className) !== "string");
+ if (typeof (next?.className) === "string" && next?.className.includes("documentView-node")) break;
if (next) child = next;
else break;
}
@@ -293,6 +292,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
const doc = Document(element.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) {
@@ -545,13 +545,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
const minimal = bounds.r - bounds.x < 100 ? true : false;
const maximizeIcon = minimal ? (
- <div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
- <FontAwesomeIcon size="lg" icon="cog" />
- </div>) : (
- <div className="documentDecorations-minimizeButton" title="Iconify" 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 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">Iconify</div></>} placement="top">
+ <div className="documentDecorations-minimizeButton" 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>);
const titleArea = this._edtingTitle ?
<>
@@ -571,11 +573,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>}
</> :
<>
- {minimal ? (null) : <div className="documentDecorations-contextMenu" key="menu" title="Show context menu" onPointerDown={this.onSettingsDown}>
+ {minimal ? (null) : <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
<FontAwesomeIcon size="lg" icon="cog" />
- </div>}
+ </div></Tooltip>}
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
- <span style={{ width: "100%", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
+ <span style={{ width: "100%", display: "inline-block", cursor: "move" }}>{`${this.selectionTitle}`}</span>
</div>
</>;
@@ -610,12 +612,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
{maximizeIcon}
{titleArea}
{SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
- <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
- {"_"}
- </div>}
- <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
+ <Tooltip title={<><div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></>} placement="top">
+ <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
+ {"_"}
+ </div></Tooltip>}
+ <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
{SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
- </div>
+ </div></Tooltip>
<div id="documentDecorations-rotation" className="documentDecorations-rotation"
onPointerDown={this.onRotateDown}> ⟲ </div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
@@ -636,10 +639,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
{seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
- title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
- <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
- </div>}
+ <Tooltip title={<><div className="dash-tooltip">tap to select containing document</div></>} placement="top">
+ <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+ onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
+ <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+ </div></Tooltip>}
<div id="documentDecorations-borderRadius" className="documentDecorations-radius"
onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 628db366f..25a87ab56 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -194,7 +194,11 @@ export class EditableView extends React.Component<EditableProps> {
ref={this._ref}
style={{ display: this.props.display, minHeight: "20px", 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 ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+ <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>
</div>
);
}
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f61f4a05e..c9d78890e 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,7 +1,7 @@
.gestureOverlay-cont {
width: 100vw;
height: 100vh;
- position: fixed;
+ position: relative;
top: 0;
left: 0;
touch-action: none;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 487467b2b..2f34cbc05 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -7,9 +7,8 @@ import { Cast, FieldValue, NumCast } from "../../fields/Types";
import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { DocServer } from "../DocServer";
import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
@@ -23,7 +22,7 @@ import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import HeightLabel from "./collections/collectionMulticolumn/MultirowHeightLabel";
+import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
@observer
export default class GestureOverlay extends Touchable {
@@ -55,6 +54,7 @@ export default class GestureOverlay extends Touchable {
@observable private showMobileInkOverlay: boolean = false;
+ private _overlayRef = React.createRef<HTMLDivElement>();
private _d1: Doc | undefined;
private _inkToTextDoc: Doc | undefined;
private _thumbDoc: Doc | undefined;
@@ -497,39 +497,26 @@ export default class GestureOverlay extends Touchable {
onPointerDown = (e: React.PointerEvent) => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
this._points.push({ X: e.clientX, Y: e.clientY });
- e.stopPropagation();
- e.preventDefault();
-
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
+ setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
}
}
@action
onPointerMove = (e: PointerEvent) => {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
- this._points.push({ X: e.clientX, Y: e.clientY });
- e.stopPropagation();
- e.preventDefault();
-
-
- if (this._points.length > 1) {
- const B = this.svgBounds;
- const initialPoint = this._points[0.];
- const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
- const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
- if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
- switch (this.Tool) {
- case ToolglassTools.RadialMenu:
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- //this.handle1PointerHoldStart(e);
- }
+ this._points.push({ X: e.clientX, Y: e.clientY });
+
+ if (this._points.length > 1) {
+ const B = this.svgBounds;
+ const initialPoint = this._points[0.];
+ const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
+ const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+ if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
+ switch (this.Tool) {
+ case ToolglassTools.RadialMenu: return true;
}
}
}
+ return false;
}
handleLineGesture = (): boolean => {
@@ -551,7 +538,7 @@ export default class GestureOverlay extends Touchable {
else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
// we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
if (this._d1.type !== "ink" && doc.type !== "ink") {
- DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link");
+ DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", "");
actionPerformed = true;
}
}
@@ -588,8 +575,6 @@ export default class GestureOverlay extends Touchable {
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
switch (this.Tool) {
case ToolglassTools.InkToText:
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
this._strokes.push(new Array(...this._points));
this._points = [];
CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
@@ -623,7 +608,7 @@ export default class GestureOverlay extends Touchable {
this.makePolygon(this.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
- if (this.InkShape !== "noRec") {
+ if (!InkOptionsMenu.Instance._keepMode) {
this.InkShape = "";
}
}
@@ -633,54 +618,35 @@ export default class GestureOverlay extends Touchable {
let actionPerformed = false;
if (result && result.Score > 0.7) {
switch (result.Name) {
- case GestureUtils.Gestures.Box:
- this.dispatchGesture(GestureUtils.Gestures.Box);
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.StartBracket:
- this.dispatchGesture(GestureUtils.Gestures.StartBracket);
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.EndBracket:
- this.dispatchGesture("endbracket");
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.Line:
- actionPerformed = this.handleLineGesture();
- break;
- case GestureUtils.Gestures.Triangle:
- this.makePolygon("triangle", true);
- break;
- case GestureUtils.Gestures.Circle:
- this.makePolygon("circle", true);
- break;
- case GestureUtils.Gestures.Rectangle:
- this.makePolygon("rectangle", true);
- break;
- case GestureUtils.Gestures.Scribble:
- console.log("scribble");
- break;
- }
- if (actionPerformed) {
- this._points = [];
+ case GestureUtils.Gestures.Box: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); break;
+ case GestureUtils.Gestures.StartBracket: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); break;
+ case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture("endbracket"); break;
+ case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break;
+ case GestureUtils.Gestures.Triangle: actionPerformed = this.makePolygon("triangle", true); break;
+ case GestureUtils.Gestures.Circle: actionPerformed = this.makePolygon("circle", true); break;
+ case GestureUtils.Gestures.Rectangle: actionPerformed = this.makePolygon("rectangle", true); break;
+ case GestureUtils.Gestures.Scribble: console.log("scribble"); break;
}
}
// if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
this.dispatchGesture(GestureUtils.Gestures.Stroke);
- this._points = [];
}
+ this._points = [];
}
} else {
this._points = [];
}
- SetActiveArrowStart("none");
- GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
- SetActiveArrowEnd("none");
- GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ //get out of ink mode after each stroke=
+ if (!InkOptionsMenu.Instance._keepMode) {
+ Doc.SetSelectedTool(InkTool.None);
+ InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
+ SetActiveArrowStart("none");
+ GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
+ SetActiveArrowEnd("none");
+ GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
+ }
}
makePolygon = (shape: string, gesture: boolean) => {
@@ -691,7 +657,7 @@ export default class GestureOverlay extends Touchable {
var bottom = Math.max(...ys);
var top = Math.min(...ys);
if (shape === "noRec") {
- return;
+ return false;
}
if (!gesture) {
//if shape options is activated in inkOptionMenu
@@ -772,11 +738,12 @@ export default class GestureOverlay extends Touchable {
this._points.push({ X: x2, Y: y2 });
// this._points.push({ X: x1, Y: y1 - 1 });
}
+ return true;
}
dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
- target?.dispatchEvent(
+ return target?.dispatchEvent(
new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
{
bubbles: true,
@@ -788,7 +755,7 @@ export default class GestureOverlay extends Touchable {
}
}
)
- );
+ ) || false;
}
getBounds = (stroke: InkData) => {
@@ -807,10 +774,11 @@ export default class GestureOverlay extends Touchable {
@computed get elements() {
const width = Number(ActiveInkWidth());
+ const rect = this._overlayRef.current?.getBoundingClientRect();
const B = this.svgBounds;
B.left = B.left - width / 2;
B.right = B.right + width / 2;
- B.top = B.top - width / 2;
+ B.top = B.top - width / 2 - (rect?.y || 0);
B.bottom = B.bottom + width / 2;
B.width += width;
B.height += width;
@@ -827,7 +795,7 @@ export default class GestureOverlay extends Touchable {
}),
this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
+ {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
</svg>]
];
}
@@ -876,7 +844,8 @@ export default class GestureOverlay extends Touchable {
render() {
return (
- <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+ <div className="gestureOverlay-cont" ref={this._overlayRef}
+ onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
{this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
{this.elements}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 34f666f62..a3a023164 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -7,6 +7,7 @@ import { List } from "../../fields/List";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, PromiseValue } from "../../fields/Types";
import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
import { DocServer } from "../DocServer";
import { DocumentType } from "../documents/DocumentTypes";
import { DictationManager } from "../util/DictationManager";
@@ -104,6 +105,7 @@ export default class KeyManager {
DictationManager.Controls.stop();
// RecommendationsBox.Instance.closeMenu();
GoogleAuthenticationManager.Instance.cancel();
+ HypothesisAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
break;
case "delete":
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7d6e7e5dd..abc698e62 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -15,6 +15,8 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
import { Scripting } from "../util/Scripting";
import { Doc } from "../../fields/Doc";
+import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
+import { action } from "mobx";
library.add(faPaintBrush);
@@ -38,10 +40,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this.props.Document.isInkMask = true;
}
+ @action
+ private formatShape = () => {
+ FormatShapePane.Instance.Pinned = true;
+ }
+
render() {
TraceMobx();
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
- const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+ // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+ const strokeWidth = Number(this.layoutDoc.strokeWidth);
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
const left = Math.min(...xs) - strokeWidth / 2;
@@ -52,14 +60,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const height = bottom - top;
const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
- const strokeColor = StrCast(this.layoutDoc.color, ActiveInkColor());
+ const strokeColor = StrCast(this.layoutDoc.color, "");
const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()),
- StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()),
- StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
+ 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, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()),
+ StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
"none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true);
return (
<svg className="inkingStroke"
@@ -72,8 +80,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
overflow: "visible",
}}
onContextMenu={() => {
- ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
- ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+ const cm = ContextMenu.Instance;
+ if (cm) {
+ cm.addItem({ description: "Analyze Stroke", 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" });
+ }
}}
><defs>
</defs>
@@ -94,9 +106,9 @@ export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkP
export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); }
export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, "none"); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, "none"); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, "none"); }
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 5b142ffda..e1ddbc533 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,6 +1,10 @@
@import "globalCssVariables";
@import "nodeModuleOverrides";
+.dash-tooltip {
+ font-size: 11px;
+ padding: 2px;
+}
.mainView-tabButtons {
position: relative;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15f818d1f..e50ff342c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,11 +1,14 @@
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
+ 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
} from '@fortawesome/free-solid-svg-icons';
import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -59,8 +62,11 @@ import { DocumentManager } from '../util/DocumentManager';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { LinkMenu } from './linking/LinkMenu';
import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { Fade } from '@material-ui/core';
import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
+import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
+import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
+import CollectionMenu from './collections/CollectionMenu';
@observer
export class MainView extends React.Component {
@@ -143,7 +149,9 @@ export class MainView extends React.Component {
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);
+ 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);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -384,7 +392,7 @@ export class MainView extends React.Component {
doc.dockingConfig ? this.openWorkspace(doc) :
CollectionDockingView.AddRightSplit(doc, libraryPath);
}
- sidebarScreenToLocal = () => new Transform(0, RichTextMenu.Instance.Pinned ? -35 : 0, 1);
+ sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (InkOptionsMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
@computed get flyout() {
@@ -461,10 +469,14 @@ export class MainView extends React.Component {
@computed get mainContent() {
const sidebar = this.userDoc?.["tabs-panelContainer"];
+ const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (InkOptionsMenu.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",
- height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
+ //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 }}>
@@ -601,22 +613,28 @@ export class MainView extends React.Component {
<SettingsManager />
<GroupManager />
<GoogleAuthenticationManager />
+ <HypothesisAuthenticationManager />
<DocumentDecorations />
- <GestureOverlay>
- <RichTextMenu key="rich" />
- {this.mainContent}
- </GestureOverlay>
- <PreviewCursor />
- <LinkCreatedBox />
+ <CollectionMenu />
+ <InkOptionsMenu />
+ <FormatShapePane />
+ <RichTextMenu key="rich" />
+ {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} 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)}
+ <GestureOverlay >
+ {this.mainContent}
+ </GestureOverlay>
+ <PreviewCursor />
+ <LinkCreatedBox />
<ContextMenu />
+ <FormatShapePane />
<RadialMenu />
<PDFMenu />
<MarqueeOptionsMenu />
- <InkOptionsMenu />
+
<OverlayView />
<TimelineMenu />
{this.snapLines}
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index b0752ffb2..ca8a6e1d7 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -3,14 +3,14 @@ import "./MetadataEntryMenu.scss";
import { observer } from 'mobx-react';
import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCastAsync } from '../../fields/Doc';
+import { Doc, Field, DocListCastAsync, DocListCast } from '../../fields/Doc';
import * as Autosuggest from 'react-autosuggest';
-import { undoBatch } from '../util/UndoManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
import { emptyFunction, emptyPath } from '../../Utils';
export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
export interface MetadataEntryProps {
- docs: DocLike | (() => DocLike);
+ docs: Doc[];
onError?: () => boolean;
suggestWithFunction?: boolean;
}
@@ -39,26 +39,13 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
let onProto: boolean = false;
let value: string | undefined = undefined;
let docs = this.props.docs;
- if (typeof docs === "function") {
- if (this.props.suggestWithFunction) {
- docs = docs();
- } else {
- return;
- }
- }
- docs = await docs;
- if (docs instanceof Doc) {
- await docs[this._currentKey];
- value = Field.toKeyValueString(docs, this._currentKey);
- } else {
- for (const doc of docs) {
- const v = await doc[this._currentKey];
- onProto = onProto || !Object.keys(doc).includes(this._currentKey);
- if (field === null) {
- field = v;
- } else if (v !== field) {
- value = "multiple values";
- }
+ for (const doc of docs) {
+ const v = await doc[this._currentKey];
+ onProto = onProto || !Object.keys(doc).includes(this._currentKey);
+ if (field === null) {
+ field = v;
+ } else if (v !== field) {
+ value = "multiple values";
}
}
if (value === undefined) {
@@ -86,27 +73,16 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
const script = KeyValueBox.CompileKVPScript(this._currentValue);
if (!script) return;
- let doc = this.props.docs;
- if (typeof doc === "function") {
- doc = doc();
- }
- doc = await doc;
-
- let success: boolean;
- if (doc instanceof Doc) {
- success = KeyValueBox.ApplyKVPScript(doc, this._currentKey, script);
- } else {
- let childSuccess = true;
- if (this._addChildren) {
- for (const document of doc) {
- const collectionChildren = await DocListCastAsync(document.data);
- if (collectionChildren) {
- childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
- }
+ let childSuccess = true;
+ if (this._addChildren) {
+ for (const document of this.props.docs) {
+ const collectionChildren = DocListCast(document.data);
+ if (collectionChildren) {
+ childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
}
}
- success = doc.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
}
+ const success = this.props.docs.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
if (!success) {
if (this.props.onError) {
if (this.props.onError()) {
@@ -132,24 +108,12 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
}
}
- getKeySuggestions = async (value: string): Promise<string[]> => {
+ getKeySuggestions = (value: string) => {
value = value.toLowerCase();
let docs = this.props.docs;
- if (typeof docs === "function") {
- if (this.props.suggestWithFunction) {
- docs = docs();
- } else {
- return [];
- }
- }
- docs = await docs;
- if (docs instanceof Doc) {
- return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
- }
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
}
getSuggestionValue = (suggestion: string) => suggestion;
@@ -157,9 +121,8 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
return (null);
}
componentDidMount() {
-
this._suggestionDispser = reaction(() => this._currentKey,
- () => this.getKeySuggestions(this._currentKey).then(action((s: string[]) => this._allSuggestions = s)),
+ () => this._allSuggestions = this.getKeySuggestions(this._currentKey),
{ fireImmediately: true });
}
componentWillUnmount() {
@@ -171,19 +134,8 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
}
private get considerChildOptions() {
- let docSource = this.props.docs;
- if (typeof docSource === "function") {
- docSource = docSource();
- }
- docSource = docSource as Doc[] | Doc;
- if (docSource instanceof Doc) {
- if (docSource._viewType === undefined) {
- return (null);
- }
- } else if (Array.isArray(docSource)) {
- if (!docSource.every(doc => doc._viewType !== undefined)) {
- return null;
- }
+ if (!this.props.docs.every(doc => doc._viewType !== undefined)) {
+ return null;
}
return (
<div style={{ display: "flex" }}>
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 26c2e0e1e..09a349012 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -3,6 +3,8 @@
overflow: hidden;
display: flex;
flex-direction: column;
+ top: 0;
+ left: 0;
}
.overlayWindow-outerDiv,
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 37d8dd23b..5c3a8185c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -124,7 +124,9 @@ export class OverlayView extends React.Component {
ele = <div key={Utils.GenerateGuid()} className="overlayView-wrapperDiv" style={{
transform: `translate(${options.x}px, ${options.y}px)`,
width: options.width,
- height: options.height
+ height: options.height,
+ top: 0,
+ left: 0
}}>{ele}</div>;
this._elements.push(ele);
return remove;
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index cdde32c21..196151e32 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -169,7 +169,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
<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)}>
+ <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>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 916e631d0..9fb8a227e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -108,8 +108,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
return100 = () => 100;
@computed get scriptField() {
- return ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
+ const script = ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
{ docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
+ return script ? () => script : undefined;
}
templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
@@ -142,8 +143,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
ContainingCollectionView={undefined}
docFilters={returnEmptyFilter}
rootSelected={returnFalse}
- onCheckedClick={this.scriptField!}
- onChildClick={this.scriptField!}
+ onCheckedClick={this.scriptField}
+ onChildClick={this.scriptField}
LibraryPath={emptyPath}
dropAction={undefined}
active={returnTrue}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f54bd3aff..eac30ca75 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -385,10 +385,10 @@ export class Timeline extends React.Component<FieldViewProps> {
const ttime = `Total: ${this.toReadTime(this._time)}`;
return (
<div style={{ flexDirection: "column" }}>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
{ctime}
</div>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
{ttime}
</div>
</div>
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 1344b70f4..8e9970ada 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -38,15 +38,15 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
panelWidth = () => this.props.PanelWidth() / 3;
panelHeight = () => this.props.PanelHeight() * 0.6;
+ onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._itemIndex);
const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
+ const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout });
+ const onChildClick = script && (() => script);
return <ContentFittingDocumentView {...this.props}
- onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
- onClick={ScriptField.MakeScript(
- "child._showCaption = 'caption'",
- { child: Doc.name },
- { child: childPair.layout })}
+ onDoubleClick={this.onChildDoubleClick}
+ onClick={onChildClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.ChildLayoutTemplate}
LayoutTemplateString={this.props.ChildLayoutString}
@@ -108,17 +108,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
}, 1500);
}
- onContextMenu = (e: React.MouseEvent): void => {
- // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped()) {
- ContextMenu.Instance.addItem({
- description: "Make Hero Image", event: () => {
- const index = NumCast(this.layoutDoc._itemIndex);
- (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
- }, icon: "plus"
- });
- }
- }
_downX = 0;
_downY = 0;
onPointerDown = (e: React.PointerEvent) => {
@@ -184,7 +173,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
const index = NumCast(this.layoutDoc._itemIndex);
const translateX = (1 - index) / this.childLayoutPairs.length * 100;
- return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
<div className="carousel-wrapper" style={{ transform: `translateX(calc(${translateX}%` }}>
{this.content}
</div>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index bd0e4fc9a..404dc0daa 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -14,6 +14,7 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { ContextMenu } from '../ContextMenu';
import { ObjectField } from '../../../fields/ObjectField';
import { returnFalse } from '../../../Utils';
+import { ScriptField } from '../../../fields/ScriptField';
type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
const CarouselDocument = makeInterface(documentSchema, collectionSchema);
@@ -40,14 +41,16 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
}
panelHeight = () => this.props.PanelHeight() - 50;
+ onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+ onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
<>
<div className="collectionCarouselView-image" key="image">
<ContentFittingDocumentView {...this.props}
- onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
- onClick={ScriptCast(this.layoutDoc.onChildClick)}
+ onDoubleClick={this.onContentDoubleClick}
+ onClick={this.onContentClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.ChildLayoutTemplate}
LayoutTemplateString={this.props.ChildLayoutString}
@@ -83,18 +86,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
</>;
}
-
- onContextMenu = (e: React.MouseEvent): void => {
- // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped()) {
- ContextMenu.Instance?.addItem({
- description: "Make Hero Image", event: () => {
- const index = NumCast(this.layoutDoc._itemIndex);
- (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
- }, icon: "plus"
- });
- }
- }
_downX = 0;
_downY = 0;
onPointerDown = (e: React.PointerEvent) => {
@@ -119,7 +110,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
}
render() {
- return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
{this.content}
{this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
</div>;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 79c577b6d..657296566 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -390,6 +390,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
reactionDisposer?: Lambda;
componentDidMount: () => void = () => {
if (this._containerRef.current) {
+ const observer = new _global.ResizeObserver(action((entries: any) => {
+ for (const entry of entries) {
+ this.onResize(null as any);
+ }
+ }));
+ observer.observe(this._containerRef.current);
this.reactionDisposer = reaction(
() => this.props.Document.dockingConfig,
() => {
@@ -430,7 +436,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const cur = this._containerRef.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
- this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+ this._goldenLayout?.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
}
@action
@@ -646,16 +652,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (this.props.renderDepth > 0) {
return <div style={{ width: "100%", height: "100%" }}>Nested workspaces can't be rendered</div>;
}
- return (
- <Measure offset onResize={this.onResize}>
- {({ measureRef }) =>
- <div ref={measureRef}>
- <div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
- </div>
- }
- </Measure>
- );
+ return <div className="collectiondockingview-container" id="menuContainer"
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />;
}
}
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 5ada79a28..b8b72e756 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -35,6 +35,18 @@
font-size: 12.5px;
}
+ .bottomPopup-descriptions {
+ display: inline;
+ white-space: nowrap;
+ padding-left: 8px;
+ padding-right: 8px;
+ vertical-align: middle;
+ background-color: lightgrey;
+ border-radius: 5.5px;
+ color: black;
+ margin-right: 5px;
+ }
+
.bottomPopup-exit {
display: inline;
white-space: nowrap;
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7cbe5c19d..407524353 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -15,6 +15,8 @@ import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
+import { Tooltip } from '@material-ui/core';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -89,22 +91,42 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
DocumentLinksButton.StartLink = undefined;
}
+ @action
+ changeDescriptionSetting = () => {
+ if (LinkDescriptionPopup.showDescriptions) {
+ if (LinkDescriptionPopup.showDescriptions === "ON") {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ } else {
+ LinkDescriptionPopup.showDescriptions = "ON";
+ }
+ } else {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+ }
+
render() {
const guid = Utils.GenerateGuid();
const flexDir: any = StrCast(this.Document.flexDirection);
const backgroundColor = StrCast(this.props.Document.backgroundColor, "black");
const color = StrCast(this.props.Document.color, "white");
+
+ const menuOpener = <label htmlFor={`${guid}`} style={{
+ background: backgroundColor === color ? "black" : backgroundColor,
+ // width: "18px", height: "18px", fontSize: "12.5px",
+ // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
+ // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <p>+</p>
+ </label>;
+
return <div className="collectionLinearView-outer">
<div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <label htmlFor={`${guid}`} title="Close Menu" style={{
- background: backgroundColor === color ? "black" : backgroundColor,
- // width: "18px", height: "18px", fontSize: "12.5px",
- // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
- // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
- }}
- onPointerDown={e => e.stopPropagation()} >
- <p>+</p>
- </label>
+ <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top">
+ {menuOpener}
+ </Tooltip>
<input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
@@ -154,9 +176,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.StartLink.title} </span>
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}
- >Exit</span>
+ Creating link from: {DocumentLinksButton.StartLink.title}
+ </span>
+
+ <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
+ "Turn on description pop-up"} </div></>} placement="top">
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+ Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ </span>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Exit link clicking mode </div></>} placement="top">
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+ Clear
+ </span>
+ </Tooltip>
{/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
onClick={this.exitLongLinks} /> */}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionMenu.scss
index b1e8d20ad..348b9e6ea 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,22 +1,36 @@
@import "../globalCssVariables";
-@import '~js-datepicker/dist/datepicker.min.css';
-.collectionViewChrome-cont {
- position: absolute;
+
+.collectionMenu-cont {
+ position:relative;
+ display:inline-flex;
width: 100%;
opacity: 0.9;
z-index: 9001;
transition: top .5s;
- background: lightgrey;
+ background: #121721;
+ color: white;
transform-origin: top left;
+ top: 0;
+ width:100%;
+
+ .antimodeMenu-button {
+ padding:0;
+ width:40px;
+ display: flex;
+ svg {
+ margin:auto;
+ }
+ }
- .collectionViewChrome {
+ .collectionMenu {
display: flex;
padding-bottom: 1px;
height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: visible;
z-index: 9001;
+ border: unset;
.collectionViewBaseChrome {
display: flex;
@@ -25,8 +39,8 @@
font-size: 75%;
//text-transform: uppercase;
//letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #121721;
+ color: white;
outline-color: black;
border: none;
//padding: 12px 10px 11px 10px;
@@ -52,21 +66,21 @@
margin-left: 3px;
margin-right: 0px;
font-size: 75%;
- background: rgb(238, 238, 238);
+ background: #121721;
border: none;
- color: grey;
+ color: white;
}
.commandEntry-outerDiv {
pointer-events: all;
- background-color: gray;
+ background-color: #121721;
display: flex;
flex-direction: row;
height: 30px;
.commandEntry-drop {
color: white;
- width: 25px;
+ width: 30px;
margin-top: auto;
margin-bottom: auto;
}
@@ -94,13 +108,7 @@
color: grey;
margin-top: auto;
margin-bottom: auto;
- margin-left: 5px;
- }
-
- .collectionViewBaseChrome-viewModes {
- margin-left: 25px;
}
-
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
@@ -109,7 +117,7 @@
position: relative;
display: flex;
margin: auto;
- background: gray;
+ background: #121721;
color: white;
width: 30px;
height: 30px;
@@ -190,8 +198,8 @@
font-size: 75%;
//text-transform: uppercase;
//letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #121721;
+ color: white;
outline-color: black;
border: none;
//padding: 12px 10px 11px 10px;
@@ -297,7 +305,7 @@
}
}
-.collectionFreeFormViewChrome-cont {
+.collectionFreeFormMenu-cont {
width: 60px;
display: flex;
position: relative;
@@ -311,7 +319,7 @@
width: 20;
height: 30;
bottom: 0;
- background: gray;
+ background: #121721;
display: flex;
align-items: center;
color: white;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionMenu.tsx
index 7f1fe7649..cdd653823 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,39 +1,63 @@
+import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, Lambda } from "mobx";
+import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
import { observer } from "mobx-react";
-import * as React from "react";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
+import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
+import AntimodeMenu from "../AntimodeMenu";
+import "./CollectionMenu.scss";
import { undoBatch } from "../../util/UndoManager";
-import { EditableView } from "../EditableView";
-import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
-import { CollectionViewType } from "./CollectionView";
-import { CollectionView } from "./CollectionView";
-import "./CollectionViewChromes.scss";
+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 { Id } from "../../../fields/FieldSymbols";
+import { listSpec } from "../../../fields/Schema";
-interface CollectionViewChromeProps {
- CollectionView: CollectionView;
- type: CollectionViewType;
- collapse?: (value: boolean) => any;
- PanelWidth: () => number;
+@observer
+export default class CollectionMenu extends AntimodeMenu {
+ static Instance: CollectionMenu;
+
+ @observable SelectedCollection: CollectionView | undefined;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ 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);
+ }
+
+ @action
+ toggleMenuPin = (e: React.MouseEvent) => {
+ Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+ if (!this.Pinned && this._left < 0) {
+ this.jumpTo(300, 300);
+ }
+ }
+
+ 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>;
+
+ return this.getElement(!this.SelectedCollection ? [button] :
+ [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+ button]);
+ }
}
-interface Filter {
- key: string;
- value: string;
- contains: boolean;
+interface CollectionMenuProps {
+ CollectionView: CollectionView;
+ type: CollectionViewType;
}
const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@observer
-export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get target() { return this.props.CollectionView.props.Document; }
@@ -99,14 +123,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
componentDidMount = action(() => {
this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
- // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
- switch (this.props.CollectionView.props.Document._chromeStatus) {
- case "disabled":
- throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
- case "collapsed":
- this.props.collapse?.(true);
- break;
- }
});
@undoBatch
@@ -130,93 +146,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
this.document._facetWidth = 0;
}
- // @action
- // openDatePicker = (e: React.PointerEvent) => {
- // if (this._picker) {
- // this._picker.alwaysShow = true;
- // this._picker.show();
- // // TODO: calendar is offset when zoomed in/out
- // // this._picker.calendar.style.position = "absolute";
- // // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
- // // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
- // // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
- // // this._picker.calendar.style.left = x;
- // // this._picker.calendar.style.top = y;
-
- // e.stopPropagation();
- // }
- // }
-
- // <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- // id={Utils.GenerateGuid()}
- // ref={this.datePickerRef}
- // value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
- // onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
- // onPointerDown={this.openDatePicker}
- // placeholder="Value" />
- // @action.bound
- // applyFilter = (e: React.MouseEvent) => {
- // const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
- // const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
- // const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
- // const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
- // const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
- // let dateRestrictionScript = "";
- // if (this._dateValue instanceof Date) {
- // const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
- // const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
- // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- // }
- // else {
- // const createdDate = new Date(this._dateValue);
- // if (!isNaN(createdDate.getTime())) {
- // const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
- // const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
- // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- // }
- // }
- // const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
- // `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
- // `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
- // "true";
-
- // this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
- // }
-
- // datePickerRef = (node: HTMLInputElement) => {
- // if (node) {
- // try {
- // this._picker = datepicker("#" + node.id, {
- // disabler: (date: Date) => date > new Date(),
- // onSelect: (instance: any, date: Date) => runInAction(() => {}), // this._dateValue = date),
- // dateSelected: new Date()
- // });
- // } catch (e) {
- // console.log("date picker exception:" + e);
- // }
- // }
- // }
-
-
- @action
- toggleCollapse = () => {
- this.document._chromeStatus = this.document._chromeStatus === "enabled" ? "collapsed" : "enabled";
- if (this.props.collapse) {
- this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
- }
- }
@computed get subChrome() {
- const collapsed = this.document._chromeStatus !== "enabled";
- if (collapsed) return null;
switch (this.props.type) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={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;
}
}
@@ -270,15 +209,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@computed get templateChrome() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
+ 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}>
<div className="commandEntry-drop">
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}
- style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}>
+ 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>
@@ -289,14 +226,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@computed get viewModes() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+ 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}>
<div className="commandEntry-drop">
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-viewPicker" style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}
+ className="collectionViewBaseChrome-viewPicker"
onPointerDown={stopPropagation}
onChange={this.viewChanged}
value={StrCast(this.props.CollectionView.props.Document._viewType)}>
@@ -315,29 +251,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
render() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
return (
- <div className="collectionViewChrome-cont" style={{
- top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
- transform: collapsed ? "" : `scale(${scale})`,
- width: `${this.props.PanelWidth() / scale}px`
- }}>
- <div className="collectionViewChrome" style={{ border: "unset", pointerEvents: collapsed ? "none" : undefined }}>
+ <div className="collectionMenu-cont" >
+ <div className="collectionMenu">
<div className="collectionViewBaseChrome">
- <button className="collectionViewBaseChrome-collapse"
- style={{
- top: collapsed ? 70 : 10,
- transform: `rotate(${collapsed ? 180 : 0}deg) scale(0.5) translate(${collapsed ? "-100%, -100%" : "0, 0"})`,
- opacity: 0.9,
- display: (collapsed && !this.props.CollectionView.props.isSelected()) ? "none" : undefined,
- left: (collapsed ? 0 : "unset"),
- }}
- title="Collapse collection chrome" onClick={this.toggleCollapse}>
- <FontAwesomeIcon icon="caret-up" size="2x" />
- </button>
{this.viewModes}
- <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+ <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
<div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
<FontAwesomeIcon icon="filter" size="2x" />
</div>
@@ -352,7 +271,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
get Document() { return this.props.CollectionView.props.Document; }
@computed get dataField() {
@@ -364,7 +283,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@undoBatch
@action
nextKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.Document.currentFrame, "number", null);
if (currentFrame === undefined) {
this.Document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -376,7 +295,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.Document.currentFrame, "number", null);
if (currentFrame === undefined) {
this.Document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -386,7 +305,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
}
render() {
return this.Document.isAnnotationOverlay ? (null) :
- <div className="collectionFreeFormViewChrome-cont">
+ <div className="collectionFreeFormMenu-cont">
<div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
<FontAwesomeIcon icon={"caret-left"} size={"lg"} />
</div>
@@ -400,9 +319,8 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
</div>;
}
}
-
@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionStackingViewChrome extends React.Component<CollectionMenuProps> {
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
@@ -502,7 +420,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
// private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
@undoBatch
@@ -549,7 +467,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
}
@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
get sortAscending() {
return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
@@ -585,7 +503,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
// Enter scroll speed for 3D Carousel
@observer
-export class Collection3DCarouselViewChrome extends React.Component<CollectionViewChromeProps> {
+export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
@computed get scrollSpeed() {
return this.props.CollectionView.props.Document._autoScrollSpeed;
}
@@ -624,7 +542,7 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionVi
* Chrome for grid view.
*/
@observer
-export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionGridViewChrome extends React.Component<CollectionMenuProps> {
private clicked: boolean = false;
private entered: boolean = false;
@@ -770,7 +688,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</span>
<select className="collectionGridViewChrome-viewPicker"
- style={{ marginRight: 5, width: this.props.PanelWidth() < 300 ? 25 : undefined }}
+ style={{ marginRight: 5 }}
onPointerDown={stopPropagation}
onChange={this.changeCompactType}
value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
@@ -797,4 +715,4 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9d8790dda..838e1f664 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -163,8 +163,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
}
- @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
+ @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
+ @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ed8535ecb..3794088d4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+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";
@@ -19,6 +19,7 @@ import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
import React = require("react");
import * as rp from 'request-promise';
+import ReactLoading from 'react-loading';
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -106,16 +107,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
[...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
}
@computed get childDocs() {
- const docFilters = this.docFilters();
- const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
- for (let i = 0; i < docFilters.length; i += 3) {
- const [key, value, modifiers] = docFilters.slice(i, i + 3);
- if (!filterFacets[key]) {
- filterFacets[key] = {};
- }
- filterFacets[key][value] = modifiers;
- }
let rawdocs: (Doc | Promise<Doc>)[] = [];
if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
@@ -128,38 +119,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
}
+
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);
- const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+ const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => {
- for (const facetKey of Object.keys(filterFacets)) {
- const facet = filterFacets[facetKey];
- const satisfiesFacet = Object.keys(facet).some(value => {
- if (facet[value] === "match") {
- return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
- }
- return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
- });
- if (!satisfiesFacet) {
- return false;
- }
- }
- return true;
- }) : childDocs;
- const rangeFilteredDocs = filteredDocs.filter(d => {
- for (let i = 0; i < docRangeFilters.length; i += 3) {
- const key = docRangeFilters[i];
- const min = Number(docRangeFilters[i + 1]);
- const max = Number(docRangeFilters[i + 2]);
- const val = Cast(d[key], "number", null);
- if (val !== undefined && (val < min || val > max)) {
- return false;
- }
- }
- return true;
- });
- return rangeFilteredDocs;
+ return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript);
}
@action
@@ -412,13 +378,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
});
}
}
+ this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY);
+ batch.end();
+ }
+ slowLoadDocuments = async (files: File[], options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number) => {
+ runInAction(() => CollectionSubViewLoader.Waiting = "block");
+ const disposer = OverlayView.Instance.addElement(
+ <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 });
generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options));
if (generatedDocuments.length) {
const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d));
if (set) {
- this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ UndoManager.RunInBatch(() => this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!), "drop");
} else {
- generatedDocuments.forEach(this.addDocument);
+ UndoManager.RunInBatch(() => generatedDocuments.forEach(this.addDocument), "drop");
}
completed?.();
} else {
@@ -426,13 +399,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
}
}
- batch.end();
+ disposer();
}
}
return CollectionSubView;
}
+export class CollectionSubViewLoader {
+ @observable public static Waiting = "none";
+}
+
import { DragManager, dropActionType } from "../../util/DragManager";
import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -440,4 +417,5 @@ import { DocumentType } from "../../documents/DocumentTypes";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
import { CollectionView } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
+import { OverlayView } from "../OverlayView";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 620b977fa..96a2e23c9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -61,8 +61,8 @@ export interface TreeViewProps {
treeViewHideHeaderFields: () => boolean;
treeViewPreventOpen: boolean;
renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
- onCheckedClick?: ScriptField;
- onChildClick?: ScriptField;
+ onCheckedClick?: () => ScriptField;
+ onChildClick?: () => ScriptField;
ignoreFields?: string[];
}
@@ -76,7 +76,7 @@ export interface TreeViewProps {
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
- private _editTitleScript: ScriptField | undefined;
+ private _editTitleScript: (() => ScriptField) | undefined;
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
@@ -124,7 +124,8 @@ class TreeView extends React.Component<TreeViewProps> {
constructor(props: any) {
super(props);
- this._editTitleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ this._editTitleScript = script && (() => script);
if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false);
}
@@ -207,7 +208,7 @@ class TreeView extends React.Component<TreeViewProps> {
if (complete.linkDragData) {
const sourceDoc = complete.linkDragData.linkSourceDocument;
const destDoc = this.doc;
- DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
+ DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", "");
e.stopPropagation();
}
const docDragData = complete.docDragData;
@@ -368,13 +369,13 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
- get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.doc.onCheckedClick); }
+ get onCheckedClick() { return this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); }
@action
bulletClick = (e: React.MouseEvent) => {
if (this.onCheckedClick && this.doc.type !== DocumentType.COL) {
// this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
- this.onCheckedClick.script.run({
+ this.onCheckedClick?.script.run({
this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
heading: this.props.containingCollection.title,
checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check",
@@ -404,6 +405,7 @@ class TreeView extends React.Component<TreeViewProps> {
contextMenuItems = () => [{ script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }];
truncateTitleWidth = () => NumCast(this.props.treeViewDoc.treeViewTruncateTitleWidth, 0);
showTitleEdit = () => ["*", this._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
+ onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.editTitleScript));
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -437,7 +439,7 @@ class TreeView extends React.Component<TreeViewProps> {
addDocTab={this.props.addDocTab}
rootSelected={returnTrue}
pinToPres={emptyFunction}
- onClick={this.props.onChildClick || this._editTitleScript}
+ onClick={this.onChildClick}
dropAction={this.props.dropAction}
moveDocument={this.move}
removeDocument={this.removeDoc}
@@ -539,8 +541,8 @@ class TreeView extends React.Component<TreeViewProps> {
treeViewPreventOpen: boolean,
renderedIds: string[],
libraryPath: Doc[] | undefined,
- onCheckedClick: ScriptField | undefined,
- onChildClick: ScriptField | undefined,
+ onCheckedClick: undefined | (() => ScriptField),
+ onChildClick: undefined | (() => ScriptField),
ignoreFields: string[] | undefined
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
@@ -658,8 +660,8 @@ class TreeView extends React.Component<TreeViewProps> {
export type collectionTreeViewProps = {
treeViewHideTitle?: boolean;
treeViewHideHeaderFields?: boolean;
- onCheckedClick?: ScriptField;
- onChildClick?: ScriptField;
+ onCheckedClick?: () => ScriptField;
+ onChildClick?: () => ScriptField;
};
@observer
@@ -780,7 +782,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...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
@@ -797,6 +799,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
onKeyPress = (e: React.KeyboardEvent) => {
console.log(e);
}
+ onChildClick = () => {
+ return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
+ }
render() {
TraceMobx();
if (!(this.doc instanceof Doc)) return (null);
@@ -839,7 +844,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
BoolCast(this.doc.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
- this.props.onChildClick || ScriptCast(this.doc.onChildClick), this.props.ignoreFields)
+ this.onChildClick, this.props.ignoreFields)
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 26abd2529..c1da23470 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -47,7 +47,7 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import CollectionMenu from './CollectionMenu';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -140,17 +140,17 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
} else {
added.map(doc => {
const context = Cast(doc.context, Doc, null);
- if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG) &&
- !DocListCast(doc.links).some(d => d.isPushpin)) {
+ if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
+ title: "pushpin",
icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
_width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
});
+ pushpin.isPushpin = true;
+ Doc.GetProto(pushpin).annotationOn = doc.annotationOn;
+ Doc.SetInPlace(doc, "annotationOn", undefined, true);
Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
- const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
- const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
- pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
doc.displayTimecode = undefined;
}
doc.context = this.props.Document;
@@ -234,16 +234,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
}
- @action
- private collapse = (value: boolean) => {
- this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
- }
-
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
- // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
- const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
- <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
- return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
+ return this.SubViewHelper(type, renderProps);
}
@@ -272,7 +264,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
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" });
- !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" });
}
onContextMenu = (e: React.MouseEvent): void => {
@@ -294,7 +286,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
}
- layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+ !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
!existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
@@ -305,16 +297,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
{ key: "onChildDoubleClick", name: "On Child Double Clicked" }];
funcs.map(func => onClicks.push({
description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
- ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
+ const alias = Doc.MakeAlias(this.props.Document);
+ DocUtils.makeCustomViewClicked(alias, undefined, func.key);
+ this.props.addDocTab(alias, "onRight");
}
}));
DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
onClicks.push({
description: `Set child ${childClick.title}`,
icon: "edit",
- event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
+ event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
}));
- !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
if (!Doc.UserDoc().noviceMode) {
const more = cm.findByDescription("More...");
@@ -470,7 +464,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
@computed get scriptField() {
const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
- return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ return script ? () => script : undefined;
}
@computed get filterView() {
TraceMobx();
@@ -523,7 +518,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ContentScaling={returnOne}
focus={returnFalse}
treeViewHideHeaderFields={true}
- onCheckedClick={this.scriptField!}
+ onCheckedClick={this.scriptField}
ignoreFields={this.ignoreFields}
annotationsKey={""}
dontRegisterView={true}
@@ -549,6 +544,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
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}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 05111adb4..8cbda310a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -16,4 +16,5 @@
stroke: rgb(0,0,0);
opacity: 0.5;
pointer-events: all;
+ cursor: move;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index a24693c30..0933d525a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,15 +1,16 @@
import { observer } from "mobx-react";
import { Doc } from "../../../../fields/Doc";
-import { Utils } from '../../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../../Utils';
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
-import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
import { observable, action, reaction, IReactionDisposer } from "mobx";
-import { StrCast, Cast } from "../../../../fields/Types";
+import { StrCast, Cast, NumCast } from "../../../../fields/Types";
import { Id } from "../../../../fields/FieldSymbols";
import { SnappingManager } from "../../../util/SnappingManager";
+import { OverlayView } from "../../OverlayView";
+import { LinkEditor } from "../../linking/LinkEditor";
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -21,17 +22,20 @@ export interface CollectionFreeFormLinkViewProps {
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
@observable _opacity: number = 0;
_anchorDisposer: IReactionDisposer | undefined;
- @action
+
+ componentWillUnmount() {
+ this._anchorDisposer?.();
+ }
componentDidMount() {
this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
- if (SnappingManager.GetIsDragging()) return;
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv) return;
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
- const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv);
+ const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv);
const a = adiv.getBoundingClientRect();
const b = bdiv.getBoundingClientRect();
const abounds = adiv.parentElement!.getBoundingClientRect();
@@ -82,12 +86,24 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
})
, { fireImmediately: true });
}
- @action
- componentWillUnmount() {
- this._anchorDisposer?.();
+
+
+ pointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, (e, down, delta) => {
+ this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
+ this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
+ return false;
+ }, emptyFunction, () => {
+ // OverlayView.Instance.addElement(
+ // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]}
+ // showLinks={action(() => { })}
+ // />, { x: 300, y: 300 });
+ });
}
+
+
render() {
- if (SnappingManager.GetIsDragging()) return null;
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) return (null);
this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
@@ -105,18 +121,22 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const pt2vec = [pt2[0] - (b.left + b.width / 2), pt2[1] - (b.top + b.height / 2)];
const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
- const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 3;
+ const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
const pt1norm = [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
const pt2norm = [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const text = StrCast(this.props.A.props.Document.linkRelationship);
- return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <text x={(Math.min(pt1[0], pt2[0]) * 2 + Math.max(pt1[0], pt2[0])) / 3} y={(pt1[1] + pt2[1]) / 2}>
- {text !== "-ungrouped-" ? text : ""}
- </text>
+ const bActive = this.props.B.isSelected() || Doc.IsBrushed(this.props.B.props.Document);
+
+ const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetX);
+ const textY = (pt1[1] + pt2[1]) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetY);
+
+ return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
+
<path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
+ <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
+ {StrCast(this.props.LinkDocs[0].description)}
+ </text>
</>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b81e400b3..994f4ce6e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -84,6 +84,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private _lastY: number = 0;
private _downX: number = 0;
private _downY: number = 0;
+ private _lastClientY: number | undefined = 0;
+ private _lastClientX: number | undefined = 0;
private _inkToTextStartX: number | undefined;
private _inkToTextStartY: number | undefined;
private _wordPalette: Map<string, string> = new Map<string, string>();
@@ -101,6 +103,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable _clusterSets: (Doc[])[] = [];
@observable _timelineRef = React.createRef<Timeline>();
+ @observable _marqueeRef = React.createRef<HTMLDivElement>();
+ @observable canPanX: boolean = true;
+ @observable canPanY: boolean = true;
+
@computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
@computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
@@ -205,7 +211,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
- if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ 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);
} else {
@@ -246,7 +252,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
} else {
const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
this.props.addDocument(source);
- linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+ linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed
e.stopPropagation();
return true;
}
@@ -575,6 +581,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onPointerUp = (e: PointerEvent): void => {
+ this._lastClientY = this._lastClientX = undefined;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
document.removeEventListener("pointermove", this.onPointerMove);
@@ -932,9 +939,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
- @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
@computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+ onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
backgroundHalo = () => BoolCast(this.Document.useClusters);
parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
@@ -1027,7 +1034,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const transform = `translate(${x}px, ${y}px)`;
if (viewDef.type === "text") {
const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
- const fontSize = Cast(viewDef.fontSize, "number");
+ const fontSize = Cast(viewDef.fontSize, "string");
return [text, x, y].some(val => val === undefined) ? undefined :
{
ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
@@ -1065,7 +1072,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
doFreeformLayout(poolData: Map<string, PoolData>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
- const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+ const initResult = this.Document.arrangeInit?.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
const state = initResult?.success ? initResult.result.scriptState : undefined;
const elements = initResult?.success ? this.viewDefsToJSX(initResult.result.views) : [];
@@ -1143,10 +1150,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
+
+ const handler = (e: any) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
+
+ document.addEventListener("dashDragging", handler);
}
+
componentWillUnmount() {
this._layoutComputeReaction?.();
+
+ const handler = (e: any) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
+ document.removeEventListener("dashDragging", handler);
}
+
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
elementFunc = () => this._layoutElements;
@@ -1155,6 +1171,43 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+
+ // <div ref={this._marqueeRef}>
+
+ @action
+ handleDragging = (e: CustomEvent<React.DragEvent>, de: DragEvent) => {
+ if ((e as any).handlePan) return;
+ (e as any).handlePan = true;
+ this._lastClientY = e.detail.clientY;
+ this._lastClientX = e.detail.clientX;
+
+ if (this._marqueeRef?.current) {
+ const dragX = e.detail.clientX;
+ const dragY = e.detail.clientY;
+ const bounds = this._marqueeRef.current?.getBoundingClientRect();
+
+ const deltaX = dragX - bounds.left < 25 ? -2 : bounds.right - dragX < 25 ? 2 : 0;
+ const deltaY = dragY - bounds.top < 25 ? -2 : bounds.bottom - dragY < 25 ? 2 : 0;
+ (deltaX !== 0 || deltaY !== 0) && this.continuePan(deltaX, deltaY);
+ }
+ e.stopPropagation();
+ }
+
+ continuePan = (deltaX: number, deltaY: number) => {
+ setTimeout(action(() => {
+ const dragY = this._lastClientY;
+ const dragX = this._lastClientX;
+ if (dragY !== undefined && dragX !== undefined && this._marqueeRef.current) {
+ const bounds = this._marqueeRef.current.getBoundingClientRect();
+ this.Document._panY = NumCast(this.Document._panY) + deltaY;
+ this.Document._panX = NumCast(this.Document._panX) + deltaX;
+ if (dragY - bounds.top < 25 || bounds.bottom - dragY < 25 || dragX - bounds.left < 25 || bounds.right - dragX < 25) {
+ this.continuePan(deltaX, deltaY);
+ }
+ } else this._lastClientY !== undefined && this._lastClientX !== undefined && this.continuePan(deltaX, deltaY);
+ }), 50);
+ }
+
promoteCollection = undoBatch(action(() => {
const childDocs = this.childDocs.slice();
childDocs.forEach(doc => {
@@ -1336,7 +1389,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return false;
});
@computed get marqueeView() {
- return <MarqueeView {...this.props}
+ return <MarqueeView
+ {...this.props}
nudge={this.nudge}
addDocTab={this.addDocTab}
activeDocuments={this.getActiveDocuments}
@@ -1346,14 +1400,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents
- centeringShiftX={this.centeringShiftX}
- centeringShiftY={this.centeringShiftY}
- transition={Cast(this.layoutDoc._viewTransition, "string", null)}
- viewDefDivClick={this.props.viewDefDivClick}
- zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- {this.children}
- </CollectionFreeFormViewPannableContents>
+ <div ref={this._marqueeRef}>
+ <CollectionFreeFormViewPannableContents
+ centeringShiftX={this.centeringShiftX}
+ centeringShiftY={this.centeringShiftY}
+ transition={Cast(this.layoutDoc._viewTransition, "string", null)}
+ viewDefDivClick={this.props.viewDefDivClick}
+ zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ {this.children}
+ </CollectionFreeFormViewPannableContents></div>
{this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
new file mode 100644
index 000000000..88876471c
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -0,0 +1,64 @@
+.antimodeMenu-button {
+ width: 200px;
+ position: relative;
+ text-align: left;
+
+ .color-previewI {
+ width: 100%;
+ height: 40%;
+ }
+
+ .color-previewII {
+ width: 100%;
+ height: 100%;
+ }
+}
+
+.antimenu-Buttonup {
+ position: absolute;
+ width: 20;
+ height: 10;
+ right: 0;
+ padding: 0;
+}
+
+.formatShapePane-inputBtn {
+ width: inherit;
+ position: absolute;
+}
+
+.sketch-picker {
+ background: #323232;
+
+ .flexbox-fit {
+ background: #323232;
+ }
+}
+
+.btn-group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ /* Make the buttons appear below each other */
+}
+
+.btn-group-palette {
+ display: block;
+ /* Make the buttons appear below each other */
+}
+
+.btn-draw {
+ display: inline;
+ /* Make the buttons appear below each other */
+}
+
+.btn2-group {
+ display: block;
+ background: #323232;
+ grid-template-columns: auto;
+
+ /* Make the buttons appear below each other */
+ .antimodeMenu-button {
+ background: #323232;
+ display: block;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
new file mode 100644
index 000000000..4e328d838
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -0,0 +1,307 @@
+import React = require("react");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Field, Opt } from "../../../../fields/Doc";
+import { Document } from "../../../../fields/documentSchemas";
+import { InkField } from "../../../../fields/InkField";
+import { BoolCast, Cast, NumCast } from "../../../../fields/Types";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { SelectionManager } from "../../../util/SelectionManager";
+import AntimodeMenu from "../../AntimodeMenu";
+import "./FormatShapePane.scss";
+import { undoBatch } from "../../../util/UndoManager";
+
+@observer
+export default class FormatShapePane extends AntimodeMenu {
+ static Instance: FormatShapePane;
+
+ 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 _currMode = "fill-drip";
+ @observable private _lock = false;
+ @observable private _fillBtn = false;
+ @observable private _lineBtn = false;
+
+ getField(key: string) {
+ return this.selectedInk?.reduce((p, i) =>
+ (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
+ }
+
+ @computed get selectedInk() {
+ const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+ return inks.length ? inks : undefined;
+ }
+ @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
+ @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
+ @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
+ @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+ @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+ @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+ @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+ @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+ @computed get shapeHgt() { return this.getField("_height"); }
+ @computed get shapeWid() { return this.getField("_width"); }
+ @computed get shapeXps() { return this.getField("x"); }
+ @computed get shapeYps() { return this.getField("y"); }
+ @computed get shapeRot() { return this.getField("rotation"); }
+ set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
+ set solidFil(value) { this.unFilled = !value; }
+ set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
+ set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
+ set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
+ set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
+ set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+ set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ set dashdStk(value) {
+ value && (this._lastDash = value) && (this.unStrokd = false);
+ this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
+ }
+ set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
+ set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
+ set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
+ set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
+ set shapeWid(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = Number(value);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
+ });
+ }
+ set shapeHgt(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = Number(value);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
+ });
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ FormatShapePane.Instance = this;
+ this._canFade = false;
+ this.Pinned = BoolCast(Doc.UserDoc()["menuFormatShape-pinned"]);
+ }
+
+ @action
+ closePane = () => {
+ this.fadeOut(false);
+ this.Pinned = false;
+ }
+
+ @action
+ upDownButtons = (dirs: string, field: string) => {
+ switch (field) {
+ case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
+ case "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 => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
+ });
+ break;
+ case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
+ });
+ break;
+ }
+ }
+
+ @undoBatch
+ @action
+ rotate = (degrees: number) => {
+ this.selectedInk?.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);
+ 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);
+ const _centerPoints: { X: number, Y: number }[] = [];
+ _centerPoints.push({ X: left, Y: top });
+
+ 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;
+ 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;
+ }
+ }
+ }));
+ }
+
+
+ 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>)}
+ </div>;
+ }
+ inputBox = (key: string, value: any, setter: (val: string) => {}) => {
+ return <>
+ <input style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+ type="text" value={value}
+ onChange={e => setter(e.target.value)}
+ autoFocus />
+ <button className="antiMenu-Buttonup" key="up" 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>
+ </>;
+ }
+
+ 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>
+ <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); }
+
+ @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); }
+ @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); }
+
+ @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 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 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>;
+
+ 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 />
+ {(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 />
+ </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;
+ return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
+ {subMenus.map((subMenu, i) =>
+ <div key={subMenu} style={{ width: "inherit" }}>
+ <button className="antimodeMenu-button" onPointerDown={action(() => this._subOpen[i + indexOffset] = !this._subOpen[i + indexOffset])}
+ style={{ backgroundColor: "121212", position: "relative", width: "inherit" }}>
+ {this._subOpen[i + indexOffset] ? "▼" : "▶︎"}
+ {subMenu}
+ </button>
+ {menuItems[i]}
+ </div>)}
+ </div>;
+ }
+
+ @computed get closeBtn() {
+ return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}>
+ X
+ </button>;
+ }
+
+ @computed get propertyGroupBtn() {
+ return <div className="antimodeMenu-button-tab" key="modes">
+ {this._mode.map(mode =>
+ <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => this._currMode = mode)}
+ style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
+ <FontAwesomeIcon icon={mode as IconProp} size="lg" />
+ </button>)}
+ </div>;
+ }
+
+ render() {
+ return this.getElementVert([this.closeBtn, this.propertyGroupBtn, this.propertyGroupItems]);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
index 753de6bef..03c6c97ab 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -25,8 +25,13 @@
/* Make the buttons appear below each other */
}
+.btn-draw {
+ display: inline;
+ /* Make the buttons appear below each other */
+}
+
.btn2-group {
- display: block;
+ display: grid;
background: #323232;
grid-template-columns: auto;
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index f47fca6ac..23e1c194a 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -3,7 +3,7 @@ import AntimodeMenu from "../../AntimodeMenu";
import { observer } from "mobx-react";
import { observable, action, computed } from "mobx";
import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
+import { ActiveInkColor, ActiveFillColor, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
import { Scripting } from "../../../util/Scripting";
import { InkTool } from "../../../../fields/InkField";
import { ColorState } from "react-color";
@@ -14,52 +14,41 @@ import { SelectionManager } from "../../../util/SelectionManager";
import { DocumentView } from "../../../views/nodes/DocumentView";
import { Document } from "../../../../fields/documentSchemas";
import { DocumentType } from "../../../documents/DocumentTypes";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons";
-
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
-
-
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+import { BoolCast } from "../../../../fields/Types";
+import FormatShapePane from "./FormatShapePane";
@observer
export default class InkOptionsMenu extends AntimodeMenu {
static Instance: InkOptionsMenu;
- private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", "none"];
+ private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
private _width = ["1", "5", "10", "100"];
- // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
- // private _icons = ["O", "∆", "ロ", "➜", "-"];
- private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
- private _icons = ["O", "∆", "ロ", "⎯", "✖︎", " "];
- //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
- private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"];
- private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
- private _arrowIcons = ["→", "↔︎", "•", "••", " "];
+ private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
+ private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
+ private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
+ private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
+
+ @observable _shapesNum = this._shape.length;
+ @observable _selected = this._shapesNum;
+
+ @observable _collapsed = false;
+ @observable _keepMode = false;
@observable _colorBtn = false;
@observable _widthBtn = false;
@observable _fillBtn = false;
- @observable _arrowBtn = false;
- @observable _dashBtn = false;
- @observable _shapeBtn = false;
-
-
constructor(props: Readonly<{}>) {
super(props);
InkOptionsMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
- }
-
- getColors = () => {
- return this._palette;
+ this.Pinned = BoolCast(Doc.UserDoc()["menuInkOptions-pinned"]);
}
@action
- changeArrow = (arrowStart: string, arrowEnd: string) => {
- SetActiveArrowStart(arrowStart);
- SetActiveArrowEnd(arrowEnd);
+ toggleMenuPin = (e: React.MouseEvent) => {
+ Doc.UserDoc()["menuInkOptions-pinned"] = this.Pinned = !this.Pinned;
}
@action
@@ -81,252 +70,125 @@ export default class InkOptionsMenu extends AntimodeMenu {
const doc = Document(element.rootDoc);
if (doc.type === DocumentType.INK) {
switch (field) {
- case "width":
- doc.strokeWidth = Number(value);
- break;
- case "color":
- doc.color = String(value);
- break;
- case "fill":
- doc.fillColor = String(value);
- break;
- case "bezier":
- // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
- break;
- case "arrowStart":
- doc.arrowStart = String(value);
- break;
- case "arrowEnd":
- doc.arrowEnd = String(value);
- break;
- case "dash":
- doc.dash = Number(value);
- default:
- break;
+ case "width": doc.strokeWidth = Number(value); break;
+ case "color": doc.color = String(value); break;
+ case "fill": doc.fillColor = String(value); break;
+ case "dash": doc.strokeDash = value;
}
}
}));
}
-
- @action
- changeBezier = (e: React.PointerEvent): void => {
- SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
- this.editProperties(0, "bezier");
- }
- @action
- changeDash = (e: React.PointerEvent): void => {
- SetActiveDash(ActiveDash() === "0" ? "2" : "0");
- this.editProperties(ActiveDash(), "dash");
- }
-
- @computed get arrowPicker() {
- var currIcon;
- for (var i = 0; i < this._arrowStart.length; i++) {
- if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
- currIcon = this._arrowIcons[i];
- if (this._arrowIcons[i] === " ") {
- currIcon = "➤";
- }
+ @computed get drawButtons() {
+ const func = action((i: number, keep: boolean) => {
+ this._keepMode = keep;
+ if (this._selected !== i) {
+ this._selected = i;
+ Doc.SetSelectedTool(InkTool.Pen);
+ SetActiveArrowStart(this._head[i]);
+ SetActiveArrowEnd(this._end[i]);
+ SetActiveBezierApprox("300");
+
+ GestureOverlay.Instance.InkShape = this._shape[i];
+ } else {
+ this._selected = this._shapesNum;
+ Doc.SetSelectedTool(InkTool.None);
+ SetActiveArrowStart("");
+ SetActiveArrowEnd("");
+ GestureOverlay.Instance.InkShape = "";
+ SetActiveBezierApprox("0");
}
- }
- var arrowPicker = <button
- className="antimodeMenu-button"
- key="arrow"
- onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
- style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
- {currIcon}
+ });
+ 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>)}
+ </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>;
- if (this._arrowBtn) {
- arrowPicker = <div className="btn2-group" key="arrows">
- {arrowPicker}
- {this._arrowStart.map((arrowStart, i) => {
- return <button
- className="antimodeMenu-button"
- key={arrowStart}
- onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
- style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
- {this._arrowIcons[i]}
- </button>;
- })}
- </div>;
- }
- return arrowPicker;
}
@computed get widthPicker() {
- var widthPicker = <button
- className="antimodeMenu-button"
- key="width"
- onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
- style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="bars" size="lg" />
- </button>;
- if (this._widthBtn) {
- widthPicker = <div className="btn2-group" key="width">
+ var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
+ return !this._widthBtn ? widthPicker :
+ <div className="btn2-group" key="width">
{widthPicker}
- {this._width.map(wid => {
- return <button
- className="antimodeMenu-button"
- key={wid}
+ {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" : "" }}>
+ style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
{wid}
- </button>;
- })}
+ </button>)}
</div>;
- }
- return widthPicker;
}
-
-
@computed get colorPicker() {
- var colorPicker = <button
- className="antimodeMenu-button"
- key="color"
- title="colorChanger"
- onPointerDown={action(e => this._colorBtn = !this._colorBtn)}
- style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="pen-nib" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
-
- </button>;
- if (this._colorBtn) {
- colorPicker = <div className="btn-group" key="color">
+ var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
+ <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
+ return !this._colorBtn ? colorPicker :
+ <div className="btn-group" key="color">
{colorPicker}
- {this._palette.map(color => {
- return <button
- className="antimodeMenu-button"
- key={color}
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color}
onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
- style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
+ style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
{/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
- <div className="color-previewII" style={{ backgroundColor: color }}></div>
- </button>;
- })}
+ <div className="color-previewII" style={{ backgroundColor: color }} />
+ </button>)}
</div>;
- }
- return colorPicker;
}
-
@computed get fillPicker() {
- var fillPicker = <button
- className="antimodeMenu-button"
- key="fill"
- title="fillChanger"
- onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="fill-drip" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
- </button>;
- if (this._fillBtn) {
- fillPicker = <div className="btn-group" key="fill">
+ var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
+ <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
+ return !this._fillBtn ? fillPicker :
+ <div className="btn-group" key="fill" >
{fillPicker}
- {this._palette.map(color => {
- return <button
- className="antimodeMenu-button"
- key={color}
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color}
onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+ style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
<div className="color-previewII" style={{ backgroundColor: color }}></div>
- </button>;
- })}
-
- </div>;
- }
- return fillPicker;
- }
+ </button>)}
- @computed get shapePicker() {
- var currIcon;
- if (GestureOverlay.Instance.InkShape === "") {
- currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
- } else {
- for (var i = 0; i < this._icons.length; i++) {
- if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
- currIcon = this._icons[i];
- }
- }
- }
- var shapePicker = <button
- className="antimodeMenu-button"
- key="shape"
- onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
- style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
- {currIcon}
- </button>;
- if (this._shapeBtn) {
- shapePicker = <div className="btn2-group" key="shape">
- {shapePicker}
- {this._buttons.map((btn, i) => {
- var ttl = btn;
- if (btn === "") {
- ttl = "no shape";
- }
- if (btn === "noRec") {
- ttl = "disable shape recognition";
- }
- return <button
- className="antimodeMenu-button"
- title={`Draw ${btn}`}
- key={ttl}
- onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
- style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
- {this._icons[i]}
- </button>;
- })}
</div>;
- }
- return shapePicker;
}
- @computed get bezierButton() {
- return <button
- className="antimodeMenu-button"
- title="Bezier changer"
- key="bezier"
- onPointerDown={e => this.changeBezier(e)}
- style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}>
- <FontAwesomeIcon icon="bezier-curve" size="lg" />
-
- </button>;
- }
-
- @computed get dashButton() {
- return <button
- className="antimodeMenu-button"
- title="dash changer"
- key="dash"
- onPointerDown={e => this.changeDash(e)}
- style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}>
- <FontAwesomeIcon icon="ellipsis-h" size="lg" />
-
+ @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>;
}
render() {
- const buttons = [
- // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>
- // <FontAwesomeIcon icon="arrows-alt" size="lg" />
- // </button>,
- this.shapePicker,
- this.bezierButton,
+ return this.getElement([
this.widthPicker,
this.colorPicker,
this.fillPicker,
- this.arrowPicker,
- this.dashButton,
- ];
- return this.getElement(buttons);
+ this.drawButtons,
+ this.formatPane,
+ <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>
+ ]);
}
}
Scripting.addGlobal(function activatePen(penBtn: any) {
if (penBtn) {
- Doc.SetSelectedTool(InkTool.Pen);
InkOptionsMenu.Instance.jumpTo(300, 300);
+ InkOptionsMenu.Instance.Pinned = true;
} else {
- Doc.SetSelectedTool(InkTool.None);
+ InkOptionsMenu.Instance.Pinned = false;
InkOptionsMenu.Instance.fadeOut(true);
}
});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 97ed74c10..2db665337 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -129,7 +129,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else if (!e.ctrlKey && !e.metaKey) {
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
- _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
+ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
_fontFamily: StrCast(Doc.UserDoc().fontFamily),
title: "-typed text-"
});
@@ -493,7 +493,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
summary._backgroundColor = "#e2ad32";
portal.layoutKey = "layout_portal";
portal.title = "document collection";
- DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing");
+ DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 72577e921..bf6958c68 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,7 +1,7 @@
import { action, computed, Lambda, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from "react";
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, Opt, WidthSym } from '../../../../fields/Doc';
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { makeInterface } from '../../../../fields/Schema';
@@ -31,7 +31,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
@observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
@observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
@computed get numCols() { return NumCast(this.props.Document.gridNumCols, 10); }
@computed get rowHeight() { return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight; }
@@ -283,8 +283,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
*/
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
- displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" });
- displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" });
+ displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" });
ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 776266ce6..21d283547 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -10,7 +10,7 @@ import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
import { CollectionSubView } from '../CollectionSubView';
-import "./collectionMulticolumnView.scss";
+import "./CollectionMulticolumnView.scss";
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
import { List } from '../../../../fields/List';
@@ -202,9 +202,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
-
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 1703ff4dc..d02088a6c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -7,7 +7,7 @@ import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../fields/Types';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils';
-import "./collectionMultirowView.scss";
+import "./CollectionMultirowView.scss";
import { computed, trace, observable, action } from 'mobx';
import { Transform } from '../../../util/Transform';
import HeightLabel from './MultirowHeightLabel';
@@ -202,8 +202,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index b47c8976e..d26b7920a 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -3,38 +3,188 @@
.linkEditor {
width: 100%;
height: auto;
- font-size: 12px; // TODO
+ font-size: 13px; // TODO
user-select: none;
}
-.linkEditor-back {
- margin-bottom: 6px;
+.linkEditor-button-back {
+ //margin-bottom: 6px;
+ border-radius: 10px;
+ width: 18px;
+ height: 18px;
+ padding: 0;
+
+ &:hover {
+ cursor: pointer;
+ }
}
.linkEditor-info {
- border-bottom: 0.5px solid $light-color-secondary;
- padding-bottom: 6px;
- margin-bottom: 6px;
+ //border-bottom: 0.5px solid $light-color-secondary;
+ //padding-bottom: 1px;
+ padding-top: 5px;
+ padding-left: 5px;
+ //margin-bottom: 6px;
display: flex;
justify-content: space-between;
+ color: black;
.linkEditor-linkedTo {
width: calc(100% - 26px);
+ padding-left: 5px;
+ padding-right: 5px;
+
+ .linkEditor-downArrow {
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
+
+.linkEditor-moreInfo {
+ margin-left: 12px;
+ padding-left: 13px;
+ padding-right: 6.5px;
+ padding-bottom: 4px;
+ font-size: 9px;
+ //font-style: italic;
+ text-decoration-color: grey;
+
+ .button {
+ color: black;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+}
+
+.linkEditor-description {
+ padding-left: 6.5px;
+ padding-right: 6.5px;
+ padding-bottom: 3.5px;
+
+ .linkEditor-description-label {
+ text-decoration-color: black;
+ color: black;
+ }
+
+ .linkEditor-description-input {
+ display: flex;
+
+ .linkEditor-description-editing {
+ min-width: 85%;
+ //border: 1px solid grey;
+ //border-radius: 4px;
+ padding-left: 2px;
+ padding-right: 2px;
+ //margin-right: 4px;
+ color: black;
+ text-decoration-color: grey;
+ }
+
+ .linkEditor-description-add-button {
+ display: inline;
+ /* float: right; */
+ border-radius: 7px;
+ font-size: 9px;
+ background-color: black;
+ /* padding: 3px; */
+ padding-top: 4px;
+ padding-left: 7px;
+ padding-bottom: 4px;
+ padding-right: 8px;
+ height: 80%;
+ color: white;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
}
}
-.linkEditor-button, .linkEditor-addbutton {
+.linkEditor-followingDropdown {
+ padding-left: 6.5px;
+ padding-right: 6.5px;
+ padding-bottom: 6px;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .linkEditor-followingDropdown-label {
+ color: black;
+ }
+
+ .linkEditor-followingDropdown-dropdown {
+
+ .linkEditor-followingDropdown-header {
+
+ border: 1px solid grey;
+ border-radius: 4px;
+ //background-color: rgb(236, 236, 236);
+ padding-left: 2px;
+ padding-right: 2px;
+ text-decoration-color: black;
+ color: rgb(94, 94, 94);
+
+ .linkEditor-followingDropdown-icon {
+ float: right;
+ color: black;
+ }
+ }
+
+ .linkEditor-followingDropdown-optionsList {
+ padding-left: 3px;
+ padding-right: 3px;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ .linkEditor-followingDropdown-option {
+ border: 0.25px solid grey;
+ //background-color: rgb(236, 236, 236);
+ padding-left: 2px;
+ padding-right: 2px;
+ color: grey;
+ text-decoration-color: grey;
+ font-size: 9px;
+ border-top: none;
+
+ &:hover {
+ background-color: rgb(187, 220, 231);
+ }
+ }
+
+ }
+ }
+
+
+}
+
+
+
+
+
+
+.linkEditor-button,
+.linkEditor-addbutton {
width: 18px;
height: 18px;
padding: 0;
// font-size: 12px;
border-radius: 10px;
+
&:disabled {
background-color: gray;
}
}
-.linkEditor-addbutton{
+
+.linkEditor-addbutton {
margin-left: 0px;
}
@@ -44,7 +194,7 @@
}
.linkEditor-group {
- background-color: $light-color-secondary;
+ background-color: $light-color-secondary;
padding: 6px;
margin: 3px 0;
border-radius: 3px;
@@ -56,7 +206,7 @@
.linkEditor-group-row-label {
margin-right: 6px;
- display:inline-block;
+ display: inline-block;
}
.linkEditor-metadata-row {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 13b9a2459..ed2bea722 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,14 +1,19 @@
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 } from "mobx";
+import { action, observable, computed, toJS } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { StrCast } from "../../../fields/Types";
+import { Doc, Opt } from "../../../fields/Doc";
+import { StrCast, DateCast } from "../../../fields/Types";
import { Utils } from "../../../Utils";
import { LinkManager } from "../../util/LinkManager";
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";
library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
@@ -281,27 +286,158 @@ interface LinkEditorProps {
@observer
export class LinkEditor extends React.Component<LinkEditorProps> {
+ @observable description = StrCast(LinkManager.currentLink?.description);
+ @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 description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
+
@action
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
this.props.showLinks();
}
+ @action
+ setDescripValue = (value: string) => {
+ if (LinkManager.currentLink) {
+ LinkManager.currentLink.description = value;
+ this.buttonColor = "rgb(62, 133, 55)";
+ setTimeout(action(() => {
+ this.buttonColor = "black";
+ }), 750);
+ return true;
+ }
+ }
+
+ @action
+ onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter") {
+ this.setDescripValue(this.description);
+ document.getElementById('input')?.blur();
+ }
+ }
+
+ @action
+ onDown = () => {
+ this.setDescripValue(this.description);
+ }
+
+ @action
+ handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.description = e.target.value;
+ }
+
+
+ @computed
+ get editDescription() {
+ return <div className="linkEditor-description">
+ <div className="linkEditor-description-label">
+ Link Label:</div>
+ <div className="linkEditor-description-input">
+ <div className="linkEditor-description-editing">
+ <input
+ style={{ width: "100%" }}
+ id="input"
+ value={this.description}
+ placeholder={"enter link label"}
+ // color={"rgb(88, 88, 88)"}
+ onKeyDown={this.onKey}
+ onChange={this.handleChange}
+ ></input>
+ </div>
+ <div className="linkEditor-description-add-button"
+ style={{ backgroundColor: this.buttonColor }}
+ onPointerDown={this.onDown}>Set</div>
+ </div></div>;
+ }
+
+ @action
+ changeDropdown = () => {
+ this.openDropdown = !this.openDropdown;
+ }
+
+ @action
+ changeFollowBehavior = (follow: string) => {
+ this.openDropdown = false;
+ Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
+ }
+
+ @computed
+ get followingDropdown() {
+ return <div className="linkEditor-followingDropdown">
+ <div className="linkEditor-followingDropdown-label">
+ Follow Behavior:</div>
+ <div className="linkEditor-followingDropdown-dropdown">
+ <div className="linkEditor-followingDropdown-header"
+ onPointerDown={this.changeDropdown}>
+ {StrCast(this.props.linkDoc.followLinkLocation, "Default")}
+ <FontAwesomeIcon className="linkEditor-followingDropdown-icon"
+ icon={this.openDropdown ? "chevron-up" : "chevron-down"}
+ size={"lg"} />
+ </div>
+ <div className="linkEditor-followingDropdown-optionsList"
+ style={{ display: this.openDropdown ? "" : "none" }}>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("Default")}>
+ Default
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("onRight")}>
+ Always open in right tab
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("inTab")}>
+ Always open in new tab
+ </div>
+ </div>
+ </div>
+ </div>;
+ }
+
+ @action
+ changeInfo = () => {
+ this.showInfo = !this.showInfo;
+ }
+
render() {
const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
const groups = [this.props.linkDoc].map(groupDoc => {
- return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
+ return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc}
+ sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
});
return !destination ? (null) : (
<div className="linkEditor">
- {this.props.hideback ? (null) : <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
<div className="linkEditor-info">
- <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
- <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
+ <Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top">
+ <button className="linkEditor-button-back"
+ style={{ display: this.props.hideback ? "none" : "" }}
+ onClick={this.props.showLinks}>
+ <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
+ </Tooltip>
+ <p className="linkEditor-linkedTo">Editing Link to: <b>{
+ destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
+ {/* <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
+ <FontAwesomeIcon icon="trash" size="sm" /></button> */}
+ <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top">
+ <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div>
+ </Tooltip>
</div>
- {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
+ {this.showInfo ? <div className="linkEditor-moreInfo">
+ <div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div>
+ <div>{this.props.linkDoc.creationDate ? <div> <b>Creation Date:</b>
+ {DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div>
+ </div> : null}
+
+ <div>{this.editDescription}</div>
+ <div>{this.followingDropdown}</div>
+
+ {/* {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>} */}
</div>
);
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 6468ccd3d..98e4171f0 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -1,22 +1,67 @@
@import "../globalCssVariables";
.linkMenu {
- width: 100%;
+ width: auto;
height: auto;
-}
-
-.linkMenu-list {
- max-height: 200px;
- overflow-y: scroll;
position: absolute;
- z-index: 10;
- background: $link-color;
- min-width: 150px
+ top: 0;
+ left: 0;
+
+ .linkMenu-list {
+
+ display: inline-block;
+
+ border: 1px solid black;
+
+ box-shadow: 3px 3px 1.5px grey;
+
+ max-height: 170px;
+ overflow-y: scroll;
+ position: relative;
+ z-index: 10;
+ background: white;
+ min-width: 170px;
+ //border-radius: 5px;
+ //padding-top: 6.5px;
+ //padding-bottom: 6.5px;
+ //padding-left: 6.5px;
+ //padding-right: 2px;
+ //width: calc(auto + 50px);
+
+ white-space: nowrap;
+ overflow-x: hidden;
+ width: 240px;
+ scrollbar-color: white;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:hover {
+ scrollbar-color: rgb(201, 239, 252);
+ }
+ }
+
+ .linkMenu-listEditor {
+
+ display: inline-block;
+
+ border: 1px solid black;
+
+ box-shadow: 3px 3px 1.5px grey;
+
+ max-height: 170px;
+ overflow-y: scroll;
+ position: relative;
+ z-index: 10;
+ background: white;
+ min-width: 170px;
+ }
}
.linkMenu-group {
border-bottom: 0.5px solid lightgray;
- padding: 5px 0;
+ //@extend: 5px 0;
&:last-child {
@@ -24,15 +69,16 @@
}
.linkMenu-group-name {
- display: flex;
+
&:hover {
p {
background-color: lightgray;
+
}
p.expand-one {
- width: calc(100% - 26px);
+ width: calc(100% + 20px);
}
.linkEditor-tableButton {
@@ -42,7 +88,7 @@
p {
width: 100%;
- padding: 4px 6px;
+ //padding: 4px 6px;
line-height: 12px;
border-radius: 5px;
font-weight: bold;
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c672511ac..2d151e9bc 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
import { DocumentView } from "../nodes/DocumentView";
import { LinkEditor } from "./LinkEditor";
@@ -11,6 +11,7 @@ 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);
@@ -25,19 +26,29 @@ interface Props {
export class LinkMenu extends React.Component<Props> {
@observable private _editingLink?: Doc;
- @observable private _linkMenuRef: Opt<HTMLDivElement | null>;
+ @observable private _linkMenuRef = React.createRef<HTMLDivElement>();
+ private _editorRef = React.createRef<HTMLDivElement>();
+
+ //@observable private _numLinks: number = 0;
+
+ // @computed get overflow() {
+ // if (this._numLinks) {
+ // return "scroll";
+ // }
+ // return "auto";
+ // }
@action
onClick = (e: PointerEvent) => {
LinkDocPreview.LinkInfo = undefined;
- if (this._linkMenuRef?.contains(e.target as any)) {
- DocumentLinksButton.EditLink = undefined;
- }
- if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
- DocumentLinksButton.EditLink = undefined;
+ if (this._linkMenuRef && !this._linkMenuRef.current?.contains(e.target as any)) {
+ if (this._editorRef && !this._editorRef.current?.contains(e.target as any)) {
+ console.log("outside click");
+ DocumentLinksButton.EditLink = undefined;
+ }
}
}
@action
@@ -78,12 +89,19 @@ export class LinkMenu extends React.Component<Props> {
render() {
const sourceDoc = this.props.docView.props.Document;
const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
- return <div className="linkMenu-list"
- ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
- {!this._editingLink ?
- this.renderAllGroups(groups) :
- <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+ 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]
+ }}>
+ <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
+ showLinks={action(() => this._editingLink = undefined)} />
+ </div>
}
+
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 7892d381b..2ae87ac13 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -11,6 +11,7 @@ import { DocumentView } from "../nodes/DocumentView";
import './LinkMenu.scss';
import { LinkMenuItem, StartLinkTargetsDrag } from "./LinkMenuItem";
import React = require("react");
+import { Cast } from "../../../fields/Types";
interface LinkMenuGroupProps {
sourceDoc: Doc;
@@ -26,6 +27,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _drag = React.createRef<HTMLDivElement>();
private _table = React.createRef<HTMLDivElement>();
+ private _menuRef = React.createRef<HTMLDivElement>();
onLinkButtonDown = (e: React.PointerEvent): void => {
e.stopPropagation();
@@ -65,7 +67,8 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
render() {
const groupItems = this.props.group.map(linkDoc => {
- const destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
+ const destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
+ LinkManager.Instance.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
if (destination && this.props.sourceDoc) {
return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]}
groupType={this.props.groupType}
@@ -74,17 +77,20 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
linkDoc={linkDoc}
sourceDoc={this.props.sourceDoc}
destinationDoc={destination}
- showEditor={this.props.showEditor} />;
+ showEditor={this.props.showEditor}
+ menuRef={this._menuRef} />;
}
});
return (
- <div className="linkMenu-group">
+ <div className="linkMenu-group" ref={this._menuRef}>
+
{/* <div className="linkMenu-group-name">
<p ref={this._drag} onPointerDown={this.onLinkButtonDown}
className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
{this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)}
</div> */}
+
<div className="linkMenu-group-wrapper">
{groupItems}
</div>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index e3ce69cd7..4e13ef8c8 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,19 +4,95 @@
// border-top: 0.5px solid $main-accent;
position: relative;
display: flex;
- font-size: 12px;
+ border-bottom: 0.5px solid black;
+
+ padding-left: 6.5px;
+ padding-right: 2px;
+
+ padding-top: 6.5px;
+ padding-bottom: 6.5px;
+
+ background-color: white;
.linkMenu-name {
position: relative;
+ width: auto;
+
+ .linkMenu-text {
+
+ padding: 4px 2px;
+ //display: inline;
+
+ .linkMenu-source-title {
+ text-decoration: none;
+ color: rgb(43, 43, 43);
+ font-size: 7px;
+ padding-bottom: 0.75px;
+
+ margin-left: 20px;
+ }
+
+
+ .linkMenu-title-wrapper {
+
+ display: flex;
+
+ .destination-icon-wrapper {
+ //border: 0.5px solid rgb(161, 161, 161);
+ margin-right: 2px;
+ padding-right: 2px;
+
+ .destination-icon {
+ float: left;
+ width: 12px;
+ height: 12px;
+ padding: 1px;
+ margin-right: 3px;
+ color: rgb(161, 161, 161);
+ }
+ }
+
+ .linkMenu-destination-title {
+ text-decoration: none;
+ color: rgb(85, 120, 196);
+ font-size: 14px;
+ padding-bottom: 2px;
+ padding-right: 4px;
+ margin-right: 4px;
+ float: right;
+
+ &:hover {
+ text-decoration: underline;
+ color: rgb(60, 90, 156);
+ //display: inline;
+ text-overflow: break;
+ cursor: pointer;
+ }
+ }
+ }
+
+ .linkMenu-description {
+ text-decoration: none;
+ font-style: italic;
+ color: rgb(95, 97, 102);
+ font-size: 10px;
+ margin-left: 20px;
+ max-width: 125px;
+ height: auto;
+ white-space: break-spaces;
+ }
+
+ p {
+ padding-right: 4px;
+ line-height: 12px;
+ border-radius: 5px;
+ //overflow-wrap: break-word;
+ user-select: none;
+ }
- p {
- padding: 4px 6px;
- line-height: 12px;
- border-radius: 5px;
- overflow-wrap: break-word;
- user-select: none;
}
+
}
.linkMenu-item-content {
@@ -32,25 +108,27 @@
}
&:hover {
+
+ background-color: rgb(201, 239, 252);
+
.linkMenu-item-buttons {
display: flex;
}
.linkMenu-item-content {
- &.expand-two p {
- width: calc(100% - 52px);
- background-color: lightgray;
- }
- &.expand-three p {
- width: calc(100% - 84px);
- background-color: lightgray;
+ .linkMenu-destination-title {
+ text-decoration: underline;
+ color: rgb(60, 90, 156);
+ //display: inline;
+ text-overflow: break;
}
}
}
}
.linkMenu-item-buttons {
+ //@extend: right;
position: absolute;
top: 50%;
right: 0;
@@ -61,7 +139,8 @@
width: 20px;
height: 20px;
margin: 0;
- margin-right: 6px;
+ margin-right: 4px;
+ padding-right: 6px;
border-radius: 50%;
pointer-events: auto;
background-color: $dark-color;
@@ -84,7 +163,7 @@
&:hover {
background: $main-accent;
- cursor: grab;
+ cursor: pointer;
}
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 04cd83ee0..0e847632b 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -15,7 +15,9 @@ import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
import { DocumentView } from '../nodes/DocumentView';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
-library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt);
+import { Tooltip } from '@material-ui/core';
+import { DocumentType } from '../../documents/DocumentTypes';
+library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
interface LinkMenuItemProps {
@@ -26,6 +28,7 @@ interface LinkMenuItemProps {
destinationDoc: Doc;
showEditor: (linkDoc: Doc) => void;
addDocTab: (document: Doc, where: string) => boolean;
+ menuRef: React.Ref<HTMLDivElement>;
}
// drag links and drop link targets (aliasing them if needed)
@@ -77,6 +80,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
@action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
onEdit = (e: React.PointerEvent): void => {
+
+ console.log("Edit");
+ LinkManager.currentLink = this.props.linkDoc;
+ console.log(this.props.linkDoc);
setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
}
@@ -110,7 +117,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
- if (this._buttonRef && this._buttonRef.current?.contains(e.target as any)) {
+ if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
+ console.log("outside click");
LinkDocPreview.LinkInfo = undefined;
}
}
@@ -147,7 +155,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
console.log("FOLLOWWW");
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
- DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+
+ if (this.props.linkDoc.followLinkLocation && this.props.linkDoc.followLinkLocation !== "Default") {
+ this.props.addDocTab(this.props.destinationDoc, StrCast(this.props.linkDoc.followLinkLocation));
+ } else {
+ DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+ }
}
@action
@@ -158,13 +171,53 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
DocumentLinksButton.EditLink = undefined;
}
+ @action
+ showLink = () => {
+ this.props.linkDoc.hidden = !this.props.linkDoc.hidden;
+ }
+
render() {
const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
const canExpand = keys ? keys.length > 0 : false;
+ const eyeIcon = this.props.linkDoc.hidden ? "eye-slash" : "eye";
+
+ let destinationIcon: FontAwesomeIconProps["icon"] = "question";
+ 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.COL: destinationIcon = "folder"; break;
+ case DocumentType.WEB: destinationIcon = "globe-asia"; break;
+ case DocumentType.SCREENSHOT: destinationIcon = "photo-video"; break;
+ case DocumentType.WEBCAM: destinationIcon = "video"; break;
+ 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;
+ default: destinationIcon = "question"; break;
+ }
+
+ const title = StrCast(this.props.destinationDoc.title).length > 18 ?
+ StrCast(this.props.destinationDoc.title).substr(0, 14) + "..." : this.props.destinationDoc.title;
+
+ // ...
+ // from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText)
+ // ...
+ const source = this.props.sourceDoc.type === DocumentType.RTF ? this.props.linkDoc.storedText ?
+ StrCast(this.props.linkDoc.storedText).length > 17 ?
+ StrCast(this.props.linkDoc.storedText).substr(0, 18)
+ : this.props.linkDoc.storedText : undefined : undefined;
+
return (
<div className="linkMenu-item">
<div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
+
<div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = {
@@ -174,18 +227,40 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
Location: [e.clientX, e.clientY + 20]
}))}
onPointerDown={this.onLinkButtonDown}>
- <p >{StrCast(this.props.destinationDoc.title)}</p>
+
+ <div className="linkMenu-text">
+ {source ? <p className="linkMenu-source-title">
+ <b>Source: {source}</b></p> : null}
+ <div className="linkMenu-title-wrapper">
+ <div className="destination-icon-wrapper" >
+ <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /></div>
+ <p className="linkMenu-destination-title"
+ onPointerDown={this.followDefault}>
+ {title}
+ </p>
+ </div>
+ {this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
+ {StrCast(this.props.linkDoc.description)}</p> : null} </div>
+
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
{canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
<FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
- {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
- <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div> */}
- <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
- <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
- <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
- <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
- </div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show link" : "Hide link"}</div></>}>
+ <div className="button" ref={this._editRef} onPointerDown={this.showLink}>
+ <FontAwesomeIcon className="fa-icon" icon={eyeIcon} size="sm" /></div>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
+ <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+ <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}>
+ <div className="button" onPointerDown={this.deleteLink}>
+ <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+ </Tooltip>
+ {/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
+ <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
</div>
</div>
{this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index c959b79f5..e9420a072 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -5,6 +5,7 @@
position: inherit;
display: flex;
pointer-events: all;
+ position: relative;
cursor: default;
.audiobox-buttons {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d5288fff6..5c921cea4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -268,6 +268,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
LayoutTemplate={undefined}
LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
ContainingCollectionDoc={this.props.Document}
+ dontRegisterView={true}
parentActive={returnTrue}
bringToFront={emptyFunction}
backgroundColor={returnTransparent} />
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a3020f912..d79e2c9ff 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -73,9 +73,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getValues(doc: Doc, time: number) {
const timecode = Math.round(time);
return ({
- x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
- y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
- opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= timecode || p === undefined ? o : 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),
+ 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),
});
}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index b3a9b6fee..57028b0ca 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -14,7 +14,7 @@ import { ViewBoxBaseComponent } from "../DocComponent";
import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox } from "../InkingStroke";
import "./ColorBox.scss";
import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import { DocumentType } from "../../documents/DocumentTypes";
type ColorDocument = makeInterface<[typeof documentSchema]>;
const ColorDocument = makeInterface(documentSchema);
@@ -63,9 +63,15 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
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>) => SetActiveInkWidth(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ SetActiveInkWidth(e.target.value);
+ SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(e.target.value));
+ }} />
<div> {ActiveInkBezierApprox() ?? 2}</div>
- <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ SetActiveBezierApprox(e.target.value);
+ SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeBezier = e.target.value);
+ }} />
<br />
<br />
</div>
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index ba075886b..6081def5d 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -2,7 +2,6 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import { Transform } from "nodemailer/lib/xoauth2";
-import "react-table/react-table.css";
import { Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
@@ -10,7 +9,6 @@ import { TraceMobx } from "../../../fields/util";
import { emptyFunction } from "../../../Utils";
import { dropActionType } from "../../util/DragManager";
import { CollectionView } from "../collections/CollectionView";
-import '../DocumentDecorations.scss';
import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import "./ContentFittingDocumentView.scss";
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index b58603978..97e714cd5 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -13,7 +13,7 @@
color: black;
text-transform: uppercase;
letter-spacing: 2px;
- font-size: 75%;
+ font-size: 10px;
transition: transform 0.2s;
text-align: center;
display: flex;
@@ -21,17 +21,26 @@
align-items: center;
&:hover {
- background: deepskyblue;
- transform: scale(1.05);
- cursor: grab;
+ // background: deepskyblue;
+ // transform: scale(1.05);
+ cursor: pointer;
}
}
+
.documentLinksButton {
background-color: $link-color;
}
+
.documentLinksButton-endLink {
border: red solid 2px;
+
+ &:hover {
+ background: deepskyblue;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
+
.documentLinksButton-startLink {
border: red solid 2px;
background-color: rgba(255, 192, 203, 0.5);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index bfd860f65..bbef48e44 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../fields/Doc";
import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
import { DragManager } from "../../util/DragManager";
-import { UndoManager } from "../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
import './DocumentLinksButton.scss';
import { DocumentView } from "./DocumentView";
import React = require("react");
@@ -11,6 +11,9 @@ import { DocUtils } from "../../documents/Documents";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LinkDocPreview } from "./LinkDocPreview";
import { LinkCreatedBox } from "./LinkCreatedBox";
+import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
+import { LinkManager } from "../../util/LinkManager";
+import { Tooltip } from "@material-ui/core";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -20,6 +23,7 @@ interface DocumentLinksButtonProps {
Offset?: number[];
AlwaysOn?: boolean;
InMenu?: boolean;
+ StartLink?: boolean;
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -27,81 +31,98 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@observable public static StartLink: DocumentView | undefined;
- @action
+ @action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
- if (this._linkButton.current !== null) {
- const linkDrag = UndoManager.StartBatch("Drag Link");
- this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
- dragComplete: dropEv => {
- const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
- if (this.props.View && linkDoc) {
- !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
-
- // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
- // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
- // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
- // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
- dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
- runInAction(() => this.props.View._link = linkDoc);
- setTimeout(action(() => this.props.View._link = undefined), 0);
- }
- linkDrag?.end();
- },
- hideSource: false
- });
- return true;
+ if (this.props.InMenu && this.props.StartLink) {
+ if (this._linkButton.current !== null) {
+ const linkDrag = UndoManager.StartBatch("Drag Link");
+ this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
+ dragComplete: dropEv => {
+ const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+ if (this.props.View && linkDoc) {
+ !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
+
+ // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
+ // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
+ // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
+ // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
+ dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
+ runInAction(() => this.props.View._link = linkDoc);
+ setTimeout(action(() => this.props.View._link = undefined), 0);
+ }
+ linkDrag?.end();
+ },
+ hideSource: false
+ });
+ return true;
+ }
+ return false;
}
return false;
}
+ @undoBatch
onLinkButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
- if (doubleTap && this.props.InMenu) {
+ if (doubleTap && this.props.InMenu && this.props.StartLink) {
//action(() => Doc.BrushDoc(this.props.View.Document));
DocumentLinksButton.StartLink = this.props.View;
- } else if (!!!this.props.InMenu) {
+ } else if (!this.props.InMenu) {
+ console.log("editing");
+ this.props.View ? console.log("view") : null;
DocumentLinksButton.EditLink = this.props.View;
DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}));
}
- @action
+ @action @undoBatch
onLinkClick = (e: React.MouseEvent): void => {
- if (this.props.InMenu) {
+ if (this.props.InMenu && this.props.StartLink) {
DocumentLinksButton.StartLink = this.props.View;
//action(() => Doc.BrushDoc(this.props.View.Document));
- } else if (!!!this.props.InMenu) {
+ } else if (!this.props.InMenu) {
DocumentLinksButton.EditLink = this.props.View;
DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}
- @action
+ @action @undoBatch
completeLink = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
- if (doubleTap) {
+ if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
// action((e: React.PointerEvent<HTMLDivElement>) => {
// Doc.UnBrushDoc(this.props.View.Document);
// });
} else {
- DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
- DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
- runInAction(() => {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 120;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ 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) {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 133;
+ LinkCreatedBox.linkCreated = true;
+
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ }
+
+ });
+ }
}
}
}));
}
- @action
+ @action @undoBatch
finishLinkClick = (e: React.MouseEvent) => {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
@@ -109,15 +130,28 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// Doc.UnBrushDoc(this.props.View.Document);
// });
} else {
- DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
- DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
- runInAction(() => {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 120;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ 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");
+ LinkManager.currentLink = linkDoc;
+
+ runInAction(() => {
+ if (linkDoc) {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 133;
+ LinkCreatedBox.linkCreated = true;
+
+ if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+ }
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ }
+ });
+ }
+ }
}
}
@@ -128,29 +162,63 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@computed
get linkButton() {
const links = DocListCast(this.props.View.props.Document.links);
- return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
- <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
- <div className={"documentLinksButton"} style={{
- backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "",
- width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
- }}
- onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
- // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
- // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
- // addDocTab: this.props.View.props.addDocTab,
- // linkSrc: this.props.View.props.Document,
- // linkDoc: links[0],
- // Location: [e.clientX, e.clientY + 20]
- // }))}
- >
- {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
- </div>
- {DocumentLinksButton.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)}
- {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"}
- style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
- </div>;
+
+ const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
+ const buttonTitle = "Tap to view links";
+ const title = this.props.InMenu ? menuTitle : buttonTitle;
+
+
+ const startLink = <img
+ style={{ width: "11px", height: "11px" }}
+ id={"startLink-icon"}
+ src={`/assets/${"startLink.png"}`} />;
+
+ const endLink = <img
+ style={{ width: "14px", height: "9px" }}
+ id={"endLink-icon"}
+ src={`/assets/${"endLink.png"}`} />;
+
+ const link = <img
+ style={{ width: "22px", height: "16px" }}
+ id={"link-icon"}
+ src={`/assets/${"link.png"}`} />;
+
+ const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
+ <div className={"documentLinksButton"} style={{
+ backgroundColor: this.props.InMenu ? "black" : "",
+ color: this.props.InMenu ? "white" : "black",
+ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
+ }}
+ onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
+ // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+ // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
+ // addDocTab: this.props.View.props.addDocTab,
+ // linkSrc: this.props.View.props.Document,
+ // linkDoc: links[0],
+ // Location: [e.clientX, e.clientY + 20]
+ // }))}
+ >
+
+ {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}
+
+ </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)}
+ {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)}
+ </div>;
+
+ return (!links.length) && !this.props.AlwaysOn ? (null) :
+ this.props.InMenu ?
+ <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
+ {linkButton}
+ </Tooltip> : !!!DocumentLinksButton.EditLink ?
+ <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
+ {linkButton}
+ </Tooltip> :
+ linkButton;
}
render() {
return this.linkButton;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b38db9a1e..c0a8b3a59 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,7 +11,7 @@ import { InkTool } from '../../../fields/InkField';
import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
@@ -43,6 +43,8 @@ import React = require("react");
import { DocumentLinksButton } from './DocumentLinksButton';
import { MobileInterface } from '../../../mobile/MobileInterface';
import { LinkCreatedBox } from './LinkCreatedBox';
+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,
@@ -66,10 +68,10 @@ export interface DocumentViewProps {
ignoreAutoHeight?: boolean;
contextMenuItems?: () => { script: ScriptField, label: string }[];
rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
- onClick?: ScriptField;
- onDoubleClick?: ScriptField;
- onPointerDown?: ScriptField;
- onPointerUp?: ScriptField;
+ onClick?: () => ScriptField;
+ onDoubleClick?: () => ScriptField;
+ onPointerDown?: () => ScriptField;
+ onPointerUp?: () => ScriptField;
treeViewDoc?: Doc;
dropAction?: dropActionType;
dragDivName?: string;
@@ -125,12 +127,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
@computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
- @computed get onClickHandler() { return this.props.onClick || Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
- @computed get onDoubleClickHandler() { return this.props.onDoubleClick || Cast(this.layoutDoc.onDoubleClick, ScriptField, null) || this.Document.onDoubleClick; }
- @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
- @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
+ @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
+ @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
+ @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
+ @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
NativeWidth = () => this.nativeWidth;
NativeHeight = () => this.nativeHeight;
+ onClickFunc = () => this.onClickHandler;
+ onDoubleClickFunc = () => this.onDoubleClickHandler;
private _firstX: number = -1;
private _firstY: number = -1;
@@ -585,16 +589,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
toggleLinkButtonBehavior = (): void => {
+ this.Document.ignoreClick = false;
if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) {
this.Document.isLinkButton = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = false);
- this.Document.ignoreClick = false;
this.Document.onClick = this.layoutDoc.onClick = undefined;
} else {
this.Document.isLinkButton = true;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
this.Document.followLinkZoom = false;
this.Document.followLinkLocation = undefined;
}
@@ -602,14 +602,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
toggleFollowInPlace = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document.isLinkButton = !this.Document.isLinkButton;
if (this.Document.isLinkButton) {
- this.Document.isLinkButton = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = false);
- } else {
- this.Document.isLinkButton = true;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
this.Document.followLinkZoom = true;
this.Document.followLinkLocation = "inPlace";
}
@@ -617,15 +612,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
toggleFollowOnRight = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document.isLinkButton = !this.Document.isLinkButton;
if (this.Document.isLinkButton) {
- this.Document.isLinkButton = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = false);
- } else {
- this.Document.isLinkButton = true;
this.Document.followLinkZoom = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
this.Document.followLinkLocation = "onRight";
}
}
@@ -637,35 +627,37 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
alert("linking to document tabs not yet supported. Drop link on document content.");
return;
}
+ const makeLink = action((linkDoc: Doc) => {
+ LinkManager.currentLink = linkDoc;
+
+ LinkCreatedBox.popupX = de.x;
+ LinkCreatedBox.popupY = de.y - 33;
+ LinkCreatedBox.linkCreated = true;
+
+ LinkDescriptionPopup.popupX = de.x;
+ LinkDescriptionPopup.popupY = de.y;
+ LinkDescriptionPopup.descriptionPopup = true;
+
+ setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500);
+ });
if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
e.stopPropagation();
de.complete.annoDragData.linkedToDoc = true;
- runInAction(() => {
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
-
- DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+ const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+ linkDoc && makeLink(linkDoc);
}
if (de.complete.linkDragData) {
e.stopPropagation();
- // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
- // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
-
- runInAction(() => {
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ 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
+ linkDoc && makeLink(linkDoc);
+ }
- de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
- (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
- { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
}
}
@@ -764,10 +756,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+ optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
- optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+
const existingOnClick = cm.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
@@ -777,14 +770,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
if (this.layoutDoc.onDragStart) {
funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
- cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+ cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
}
const more = cm.findByDescription("More...");
@@ -811,9 +804,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// a.download = `DocExport-${this.props.Document[Id]}.zip`;
// a.click();
});
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
}
- moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
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" });
@@ -821,19 +813,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/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" });
- cm.addItem({ description: "Help...", 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("addOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
- aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
- !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+ //!Doc.UserDoc().novice && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ 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("addOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+ // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
// const recommender_subitems: ContextMenuProps[] = [];
@@ -1064,10 +1056,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
select={this.select}
- onClick={this.onClickHandler}
+ onClick={this.onClickFunc}
layoutKey={this.finalLayoutKey} />
- {this.layoutDoc.showLinks ? this.anchors : (null)}
- {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
+ {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
+ {/* {this.allAnchors} */}
+ {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
</div>
);
}
@@ -1089,12 +1082,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true)
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
- @computed get anchors() {
+
+ @computed 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.props.dontRegisterView ? (null) : // view that are not registered
- DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+ DocUtils.FilterDocs(LinkManager.Instance.getAllDirectLinks(this.Document), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
<DocumentView {...this.props} key={i + 1}
Document={d}
ContainingCollectionView={this.props.ContainingCollectionView}
@@ -1112,10 +1107,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@computed get innards() {
TraceMobx();
- if (this.props.treeViewDoc && !this.props.LayoutTemplateString) { // this happens when the document is a tree view label (but not an anchor dot)
+ 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 }}>
{StrCast(this.props.Document.title)}
- {this.anchors}
+ {this.allAnchors}
</div>;
}
@@ -1132,7 +1127,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
select={this.select}
- onClick={this.onClickHandler}
+ onClick={this.onClickFunc}
layoutKey={this.finalLayoutKey} />
</div>);
const titleView = (!showTitle ? (null) :
@@ -1238,7 +1233,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
background: finalColor,
opacity: finalOpacity,
fontFamily: StrCast(this.Document._fontFamily, "inherit"),
- fontSize: Cast(this.Document._fontSize, "number", null)
+ fontSize: Cast(this.Document._fontSize, "string", null)
}}>
{this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
{this.innards}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c57738361..48e1f6ce3 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -25,7 +25,7 @@ export interface FieldViewProps {
Document: Doc;
DataDoc?: Doc;
LibraryPath: Doc[];
- onClick?: ScriptField;
+ onClick?: () => ScriptField;
dropAction: dropActionType;
backgroundHalo?: () => boolean;
docFilters: () => string[];
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 68b00a5be..fe0f067ad 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -18,6 +18,7 @@
text-align: center;
font-size: 8px;
margin-top:4px;
+ letter-spacing: normal;
}
svg {
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 5e8dd2497..86e9a4527 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -67,7 +67,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
}}>
<FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
- {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 5)} </div>}
+ {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 6)} </div>}
</button>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d16aa528c..4eba21eab 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -157,29 +157,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
- funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
- funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
- // funcs.push({
- // description: "Reset Native Dimensions", event: action(async () => {
- // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
- // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
- // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
- // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
- // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
- // } else {
- // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
- // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
- // }
- // }), icon: "expand-arrows-alt"
- // });
-
- const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
- const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
- modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
- modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
- //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
- !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+ funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
+ if (!Doc.UserDoc().noviceMode) {
+ funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+ funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
+ // funcs.push({
+ // description: "Reset Native Dimensions", event: action(async () => {
+ // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+ // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
+ // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
+ // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
+ // } else {
+ // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
+ // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
+ // }
+ // }), icon: "expand-arrows-alt"
+ // });
+
+ const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
+ const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
+ modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
+ modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
+ //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
+ !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+ }
ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 360ead18e..0dfbdc5cf 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
}, icon: "trash"
});
- ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+ ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "asterisk" });
}
@undoBatch
@@ -67,7 +67,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
<div className="labelBox-mainButton" style={{
background: StrCast(this.layoutDoc.backgroundColor),
color: StrCast(this.layoutDoc.color, "inherit"),
- fontSize: NumCast(this.layoutDoc._fontSize) || "inherit",
+ fontSize: StrCast(this.layoutDoc._fontSize) || "inherit",
fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
letterSpacing: StrCast(this.layoutDoc.letterSpacing),
textTransform: StrCast(this.layoutDoc.textTransform) as any,
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
index d157d3fca..648ae23c8 100644
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ b/src/client/views/nodes/LinkCreatedBox.tsx
@@ -11,8 +11,8 @@ import { Fade } from "@material-ui/core";
export class LinkCreatedBox extends React.Component<{}> {
@observable public static linkCreated: boolean = false;
- @observable public static popupX: number = 600;
- @observable public static popupY: number = 250;
+ @observable public static popupX: number = 500;
+ @observable public static popupY: number = 150;
@action
public static changeLinkCreated = () => {
@@ -23,8 +23,8 @@ export class LinkCreatedBox extends React.Component<{}> {
return <Fade in={LinkCreatedBox.linkCreated}>
<div className="linkCreatedBox-fade"
style={{
- left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 600,
- top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 250,
+ left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
+ top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
}}>Link Created</div>
</Fade>;
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
new file mode 100644
index 000000000..d92823ccc
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -0,0 +1,77 @@
+.linkDescriptionPopup {
+
+ display: flex;
+
+ border: 1px solid rgb(170, 26, 26);
+
+ width: auto;
+ position: absolute;
+
+ height: auto;
+ z-index: 10000;
+ border-radius: 10px;
+ font-size: 12px;
+ //white-space: nowrap;
+
+ background-color: rgba(250, 250, 250, 0.95);
+ padding-top: 9px;
+ padding-bottom: 9px;
+ padding-left: 9px;
+ padding-right: 9px;
+
+ .linkDescriptionPopup-input {
+ float: left;
+ background-color: rgba(250, 250, 250, 0.95);
+ color: rgb(100, 100, 100);
+ border: none;
+ min-width: 160px;
+ }
+
+ .linkDescriptionPopup-btn {
+
+ float: right;
+
+ justify-content: center;
+ vertical-align: middle;
+
+
+ .linkDescriptionPopup-btn-dismiss {
+ background-color: white;
+ color: black;
+ display: inline;
+ right: 0;
+ border-radius: 10px;
+ border: 1px solid black;
+ padding: 3px;
+ font-size: 9px;
+ text-align: center;
+ position: relative;
+ margin-right: 4px;
+ justify-content: center;
+
+ &:hover{
+ cursor: pointer;
+ }
+ }
+
+ .linkDescriptionPopup-btn-add {
+ background-color: black;
+ color: white;
+ display: inline;
+ right: 0;
+ border-radius: 10px;
+ border: 1px solid black;
+ padding: 3px;
+ font-size: 9px;
+ text-align: center;
+ position: relative;
+ justify-content: center;
+
+ &:hover{
+ cursor: pointer;
+ }
+ }
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
new file mode 100644
index 000000000..06e8d30d1
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -0,0 +1,65 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import "./LinkDescriptionPopup.scss";
+import { observable, action } from "mobx";
+import { EditableView } from "../EditableView";
+import { LinkManager } from "../../util/LinkManager";
+import { LinkCreatedBox } from "./LinkCreatedBox";
+
+
+@observer
+export class LinkDescriptionPopup extends React.Component<{}> {
+
+ @observable public static descriptionPopup: boolean = false;
+ @observable public static showDescriptions: string = "ON";
+ @observable public static popupX: number = 700;
+ @observable public static popupY: number = 350;
+ @observable description: string = "";
+ @observable popupRef = React.createRef<HTMLDivElement>();
+
+ @action
+ descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ LinkManager.currentLink && (LinkManager.currentLink.description = e.currentTarget.value);
+ }
+
+ @action
+ onDismiss = () => {
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+
+ @action
+ onClick = (e: PointerEvent) => {
+ if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+ LinkDescriptionPopup.descriptionPopup = false;
+ LinkCreatedBox.linkCreated = false;
+ }
+ }
+
+ @action
+ componentDidMount() {
+ document.addEventListener("pointerdown", this.onClick);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("pointerdown", this.onClick);
+ }
+
+ render() {
+ return <div className="linkDescriptionPopup" ref={this.popupRef}
+ style={{
+ left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
+ top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+ }}>
+ <input className="linkDescriptionPopup-input" onKeyPress={e => e.key === "Enter" && this.onDismiss()}
+ 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>
+ <div className="linkDescriptionPopup-btn-add"
+ onPointerDown={this.onDismiss}> Add </div>
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 197dc8df4..1caa82380 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -98,23 +98,11 @@ export class LinkDocPreview extends React.Component<Props> {
{this._toolTipText}
</div>
</div> :
- // <div style={{
- // border: "6px solid white",
- // }}>
- // <div style={{ backgroundColor: "white" }}> {this._targetDoc.title}
- // <div className="wrapper" style={{ float: "right" }}>
- // <div title="Delete link" className="button" style={{ display: "inline" }} ref={this._editRef} onPointerDown={this.deleteLink}>
- // <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
- // <div title="Follow link" className="button" style={{ display: "inline" }} onClick={this.followDefault} onContextMenu={this.onContextMenu}>
- // <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
- // </div>
- // </div>
- // </div>
+
<ContentFittingDocumentView
Document={this._targetDoc}
LibraryPath={emptyPath}
fitToBox={true}
- backgroundColor={this.props.backgroundColor}
moveDocument={returnFalse}
rootSelected={returnFalse}
ScreenToLocalTransform={Transform.Identity}
@@ -128,16 +116,15 @@ export class LinkDocPreview extends React.Component<Props> {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={0}
- PanelWidth={this.width}
- PanelHeight={this.height}
+ PanelWidth={() => this.width() - 16} //Math.min(350, NumCast(target._width, 350))}
+ PanelHeight={() => this.height() - 16} //Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
ContentScaling={returnOne}
NativeWidth={returnZero}
NativeHeight={returnZero}
- />;
- //</div>;
+ backgroundColor={this.props.backgroundColor} />;
}
render() {
@@ -145,7 +132,10 @@ export class LinkDocPreview extends React.Component<Props> {
style={{
position: "absolute", left: this.props.location[0],
top: this.props.location[1], width: this.width(), height: this.height(),
- boxShadow: "black 2px 2px 1em", zIndex: 1000
+ zIndex: 1000,
+ border: "8px solid white", borderRadius: "7px",
+ boxShadow: "3px 3px 1.5px grey",
+ borderBottom: "8px solid white", borderRight: "8px solid white"
}}>
{this.targetDocView}
</div>;
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 9a1aefba9..45cdfc5ad 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -56,7 +56,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
style={{ boxShadow: this.layoutDoc.opacity === 0 ? undefined : StrCast(this.layoutDoc.boxShadow, "") }}>
<div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
- fontSize: NumCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
+ fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
}} >
<Slider
mode={2}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc63dfbf5..f87b28c7f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -174,19 +174,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
+
Array.from(this.linkOnDeselect.entries()).map(entry => {
const key = entry[0];
const value = entry[1];
+
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
DocServer.GetRefField(value).then(doc => {
DocServer.GetRefField(id).then(linkDoc => {
this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
- if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
- else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+ if (linkDoc) {
+ (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc;
+ } else {
+ DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id);
+ }
});
});
});
+
this.linkOnDeselect.clear();
}
@@ -473,7 +479,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
});
changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
- appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+ appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
const uicontrols: ContextMenuProps[] = [];
uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
@@ -483,10 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
});
- appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+ appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" });
this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
- appearanceItems.push({
+
+ !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+
+ const funcs: ContextMenuProps[] = [];
+
+ const highlighting: ContextMenuProps[] = [];
+ ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+ highlighting.push({
+ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ e.stopPropagation();
+ if (FormattedTextBox._highlights.indexOf(option) === -1) {
+ FormattedTextBox._highlights.push(option);
+ } else {
+ FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+ }
+ this.updateHighlights();
+ }, icon: "expand-arrows-alt"
+ }));
+ funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
+ funcs.push({
description: "Convert to be a template style", event: () => {
if (!this.layoutDoc.isTemplateDoc) {
const title = StrCast(this.rootDoc.title);
@@ -511,29 +536,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
}, icon: "eye"
});
- !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
-
- const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
-
- const highlighting: ContextMenuProps[] = [];
- ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
- highlighting.push({
- description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
- e.stopPropagation();
- if (FormattedTextBox._highlights.indexOf(option) === -1) {
- FormattedTextBox._highlights.push(option);
- } else {
- FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
- }
- this.updateHighlights();
- }, icon: "expand-arrows-alt"
- }));
- funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
-
+ funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
this._downX = this._downY = Number.NaN;
}
@@ -944,6 +950,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
+
+
function addLinkMark(node: Node, title: string, linkId: string) {
if (!node.isText) {
const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
@@ -1168,7 +1176,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
} else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
- this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos!)));
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
}
}
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
@@ -1343,9 +1351,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const scale = 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;
- if (this.props.isSelected()) {
- setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0);
- } else if (FormattedTextBoxComment.textBox === this) {
+ 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
+ if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
setTimeout(() => FormattedTextBoxComment.Hide(), 0);
}
const selPad = this.props.isSelected() ? -10 : 0;
@@ -1368,7 +1375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
pointerEvents: interactive ? undefined : "none",
- fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+ fontSize: Cast(this.layoutDoc._fontSize, "string", null),
fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
transition: "opacity 1s"
}}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 9089e7039..6a403cb17 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -4,10 +4,74 @@
z-index: 20;
background: white;
border: 1px solid silver;
- border-radius: 2px;
+ border-radius: 7px;
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
+ box-shadow: 3px 3px 1.5px grey;
+
+ .FormattedTextBoxComment {
+ background-color: white;
+ border: 8px solid white;
+
+ //display: flex;
+ .FormattedTextBoxComment-info {
+
+ margin-bottom: 9px;
+
+ .FormattedTextBoxComment-title {
+ padding-right: 4px;
+ float: left;
+
+ .FormattedTextBoxComment-description {
+ text-decoration: none;
+ font-style: italic;
+ color: rgb(95, 97, 102);
+ font-size: 10px;
+ padding-bottom: 4px;
+ margin-bottom: 5px;
+
+ }
+ }
+
+ .FormattedTextBoxComment-button {
+ display: inline;
+ padding-left: 6px;
+ padding-right: 6px;
+ padding-top: 2.5px;
+ padding-bottom: 2.5px;
+ width: 17px;
+ height: 17px;
+ margin: 0;
+ margin-right: 3px;
+ border-radius: 50%;
+ pointer-events: auto;
+ background-color: rgb(0, 0, 0);
+ color: rgb(255, 255, 255);
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+ font-size: 12px;
+
+ &:hover {
+ background-color: rgb(77, 77, 77);
+ cursor: pointer;
+ }
+ }
+ }
+
+ .FormattedTextBoxComment-preview-wrapper {
+ width: 170px;
+ height: 170px;
+ overflow: hidden;
+ //padding-top: 5px;
+ margin-top: 10px;
+ margin-bottom: 8px;
+ align-content: center;
+ justify-content: center;
+ background-color: rgb(160, 160, 160);
+ }
+ }
}
.FormattedTextBox-tooltip:before {
@@ -42,64 +106,4 @@
top: 50%;
right: 0;
transform: translateY(-50%);
-
- .FormattedTextBoxComment-button {
- width: 20px;
- height: 20px;
- margin: 0;
- margin-right: 6px;
- border-radius: 50%;
- pointer-events: auto;
- background-color: rgb(38, 40, 41);
- color: rgb(178, 181, 184);
- font-size: 65%;
- transition: transform 0.2s;
- text-align: center;
- position: relative;
-
- // margin-top: "auto";
- // margin-bottom: "auto";
- // background: black;
- // color: white;
- // display: inline-block;
- // border-radius: 18px;
- // font-size: 12.5px;
- // width: 18px;
- // height: 18px;
- // margin-top: auto;
- // margin-bottom: auto;
- // margin-right: 3px;
- // cursor: pointer;
- // transition: transform 0.2s;
-
- .FormattedTextBoxComment-fa-icon {
- margin-top: "auto";
- margin-bottom: "auto";
- background: black;
- color: white;
- display: inline-block;
- border-radius: 18px;
- font-size: 12.5px;
- width: 18px;
- height: 18px;
- margin-top: auto;
- margin-bottom: auto;
- margin-right: 3px;
- cursor: pointer;
- transition: transform 0.2s;
- // position: absolute;
- // top: 50%;
- // left: 50%;
- // transform: translate(-50%, -50%);
- }
-
- &:last-child {
- margin-right: 0;
- }
-
- &:hover {
- background: rgb(53, 146, 199);
- ;
- }
- }
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 56826e5c7..3687d5513 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -3,7 +3,7 @@ import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../../fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
@@ -21,6 +21,7 @@ import { action } from "mobx";
import { LinkManager } from "../../../util/LinkManager";
import { LinkDocPreview } from "../LinkDocPreview";
import { DocumentLinksButton } from "../DocumentLinksButton";
+import { Tooltip } from "@material-ui/core";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -85,13 +86,13 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
FormattedTextBoxComment.tooltip.style.maxWidth = "200px";
- FormattedTextBoxComment.tooltip.style.maxHeight = "206px";
+ FormattedTextBoxComment.tooltip.style.maxHeight = "235px";
FormattedTextBoxComment.tooltip.style.width = "100%";
FormattedTextBoxComment.tooltip.style.height = "100%";
FormattedTextBoxComment.tooltip.style.overflow = "hidden";
FormattedTextBoxComment.tooltip.style.display = "none";
FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
- FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
+ FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => {
const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
const textBox = FormattedTextBoxComment.textBox;
if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
@@ -103,8 +104,22 @@ export class FormattedTextBoxComment {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
} else {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ?
+ Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc))
+ || FormattedTextBoxComment.linkDoc);
+ const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+
+ if (FormattedTextBoxComment.linkDoc.follow) {
+ if (FormattedTextBoxComment.linkDoc.follow === "Default") {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+ } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") {
+ if (target) { textBox.props.addDocTab(target, "onRight"); }
+ } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") {
+ if (target) { textBox.props.addDocTab(target, "inTab"); }
+ }
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+ }
}
} else {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
@@ -241,55 +256,36 @@ export class FormattedTextBoxComment {
}
if (target?.author) {
FormattedTextBoxComment.showCommentbox("", view, nbef);
- const docPreview = <div style={{ backgroundColor: "white", border: "8px solid white" }}>
- {target.title}
- <div className="wrapper" style={{ float: "right" }}>
- <div title="Delete link" className="FormattedTextBoxComment-button" style={{
- display: "inline",
- paddingLeft: "6px",
- paddingRight: "6px",
- paddingTop: "2.5px",
- paddingBottom: "2.5px",
- width: "20px",
- height: "20px",
- margin: 0,
- marginRight: "6px",
- borderRadius: "50%",
- pointerEvents: "auto",
- backgroundColor: "rgb(38, 40, 41)",
- color: "rgb(178, 181, 184)",
- transition: "transform 0.2s",
- textAlign: "center",
- position: "relative"
- }} ref={(r) => this._deleteRef = r}>
- <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash"
- size="sm" /></div>
- <div title="Follow link" className="FormattedTextBoxComment-button" style={{
- display: "inline",
- paddingLeft: "6px",
- paddingRight: "6px",
- paddingTop: "2.5px",
- paddingBottom: "2.5px",
- width: "20px",
- height: "20px",
- margin: 0,
- marginRight: "6px",
- borderRadius: "50%",
- pointerEvents: "auto",
- backgroundColor: "rgb(38, 40, 41)",
- color: "rgb(178, 181, 184)",
- transition: "transform 0.2s",
- textAlign: "center",
- position: "relative"
- }} ref={(r) => this._followRef = r}>
- <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right"
- size="sm" />
+
+ const title = StrCast(target.title).length > 16 ?
+ StrCast(target.title).substr(0, 16) + "..." : target.title;
+
+
+ const docPreview = <div className="FormattedTextBoxComment">
+ <div className="FormattedTextBoxComment-info">
+ <div className="FormattedTextBoxComment-title">
+ {title}
+ {FormattedTextBoxComment.linkDoc.description !== "" ? <p className="FormattedTextBoxComment-description">
+ {StrCast(FormattedTextBoxComment.linkDoc.description)}</p> : null}
</div>
- </div>
- <div className="wrapper" style={{
- maxWidth: "180px", maxHeight: "168px", overflow: "hidden",
- overflowY: "hidden", paddingTop: "5px"
- }}>
+ <div className="wrapper" style={{ float: "right" }}>
+
+ <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
+ <div className="FormattedTextBoxComment-button"
+ ref={(r) => this._deleteRef = r}>
+ <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white"
+ size="sm" /></div>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
+ <div className="FormattedTextBoxComment-button"
+ ref={(r) => this._followRef = r}>
+ <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white"
+ size="sm" />
+ </div>
+ </Tooltip>
+ </div> </div>
+ <div className="FormattedTextBoxComment-preview-wrapper">
<ContentFittingDocumentView
Document={target}
LibraryPath={emptyPath}
@@ -307,17 +303,20 @@ export class FormattedTextBoxComment {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={0}
- PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
- PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+ PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))}
+ PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
ContentScaling={returnOne}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
+ NativeWidth={() => target._nativeWidth ? NumCast(target._nativeWidth) : 0}
+ NativeHeight={() => target._nativeHeight ? NumCast(target._nativeHeight) : 0}
/>
</div>
</div>;
+
+
+
FormattedTextBoxComment.showCommentbox("", view, nbef);
ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 3f73ec436..172cde3e0 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -11,6 +11,7 @@ import { Doc, DataSym } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
import { Docs } from "../../../documents/Documents";
+import { Utils } from "../../../../Utils";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -102,7 +103,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
//Command to create a new Tab with a PDF of all the command shortcuts
bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- const newDoc = Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 });
+ const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 });
props.addDocTab(newDoc, "onRight");
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9075a6486..63f6fdc54 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -11,7 +11,7 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc } from "../../../../fields/Doc";
import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
+import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";
import { unimplementedFunction, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { LinkManager } from "../../../util/LinkManager";
@@ -24,6 +24,7 @@ import "./RichTextMenu.scss";
import { schema } from "./schema_rts";
import { TraceMobx } from "../../../../fields/util";
import { UndoManager } from "../../../util/UndoManager";
+import { Tooltip } from "@material-ui/core";
const { toggleMark } = require("prosemirror-commands");
library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
@@ -112,6 +113,7 @@ export default class RichTextMenu extends AntimodeMenu {
{ node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
{ node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
{ node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
+ { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "<none>", command: this.changeListType },
//{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
];
@@ -217,9 +219,9 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveAlignment() {
- if (this.view) {
+ 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 -= 3) {
+ for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
return path[i].attrs.align || "left";
}
@@ -230,15 +232,18 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveListStyle() {
- if (this.view) {
+ if (this.view && this.TextView.props.isSelected(true)) {
const path = (this.view.state.selection.$from as any).path;
for (let i = 0; i < path.length; i += 3) {
if (path[i].type === this.view.state.schema.nodes.ordered_list) {
return path[i].attrs.mapStyle;
}
}
+ if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) {
+ return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
+ }
}
- return "decimal";
+ return "";
}
// finds font sizes and families in selection
@@ -247,16 +252,21 @@ export default class RichTextMenu extends AntimodeMenu {
const activeFamilies: string[] = [];
const activeSizes: string[] = [];
- const state = this.view.state;
- const pos = this.view.state.selection.$from;
- const ref_node = this.reference_node(pos);
- if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
- ref_node.marks.forEach(m => {
- m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
- m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
- });
+ if (this.TextView.props.isSelected(true)) {
+ const state = this.view.state;
+ const pos = this.view.state.selection.$from;
+ const ref_node = this.reference_node(pos);
+ if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+ ref_node.marks.forEach(m => {
+ m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+ m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+ });
+ }
+ !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))));
}
-
+ !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily)));
+ !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize)));
return { activeFamilies, activeSizes };
}
@@ -269,14 +279,14 @@ export default class RichTextMenu extends AntimodeMenu {
//finds all active marks on selection in given group
getActiveMarksOnSelection() {
- if (!this.view) return;
+ let activeMarks: MarkType[] = [];
+ if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks;
const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
//current selection
const { empty, ranges, $to } = this.view.state.selection as TextSelection;
const state = this.view.state;
- let activeMarks: MarkType[] = [];
if (!empty) {
activeMarks = markGroup.filter(mark => {
const has = false;
@@ -348,22 +358,20 @@ export default class RichTextMenu extends AntimodeMenu {
}
return (
- <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
- <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
- </button>
+ <Tooltip title={<div className="dash-tooltip">{title}</div>} key={title} placement="bottom">
+ <button className={"antimodeMenu-button" + (isActive ? " active" : "")} onPointerDown={onClick}>
+ <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+ </button>
+ </Tooltip>
);
}
- createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+ createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
}
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
});
const self = this;
@@ -372,37 +380,47 @@ export default class RichTextMenu extends AntimodeMenu {
e.preventDefault();
self.TextView.endUndoTypingBatch();
options.forEach(({ label, mark, command }) => {
- if (e.target.value === label) {
- UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
+ if (e.target.value === label && mark) {
+ if (!self.TextView.props.isSelected(true)) {
+ switch (mark.type) {
+ case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
+ case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"); break;
+ }
+ }
+ else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
}
});
}
- return <select onChange={onChange} key={key}>{items}</select>;
+ return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+ <select onChange={onChange} value={activeOption}>{items}</select>
+ </Tooltip>;
}
- createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
- const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1";
+ createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
+ const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>";
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
}
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
});
const self = this;
function onChange(val: string) {
self.TextView.endUndoTypingBatch();
options.forEach(({ label, node, command }) => {
- if (val === label) {
- UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
+ if (val === label && node) {
+ if (self.TextView.props.isSelected(true)) {
+ UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
+ setter(val);
+ }
}
});
}
- return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
+
+ return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+ <select value={activeOption} onChange={e => onChange(e.target.value)}>{items}</select>
+ </Tooltip>;
}
changeFontSize = (mark: Mark, view: EditorView) => {
@@ -416,10 +434,21 @@ export default class RichTextMenu extends AntimodeMenu {
// TODO: remove doesn't work
//remove all node type and apply the passed-in one to the selected text
changeListType = (nodeType: Node | undefined) => {
- if (!this.view) return;
+ if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
+
+ const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
+ let inList: any = undefined;
+ let fromList = -1;
+ const path: any = Array.from((this.view.state.selection.$from as any).path);
+ for (let i = 0; i < path.length; i++) {
+ if (path[i]?.type === schema.nodes.ordered_list) {
+ inList = path[i];
+ fromList = path[i - 1];
+ }
+ }
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+ if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -427,12 +456,12 @@ export default class RichTextMenu extends AntimodeMenu {
this.view!.dispatch(tx2);
})) {
const tx2 = this.view.state.tr;
- if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to);
+ if (nodeType && (inList || nextIsOL)) {
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from,
+ inList ? fromList + inList.nodeSize : this.view.state.selection.to);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
-
- this.view.dispatch(tx3.setSelection(new NodeSelection(tx3.doc.resolve(this.view.state.selection.$from.pos))));
+ this.view.dispatch(tx3);
}
}
}
@@ -448,13 +477,13 @@ export default class RichTextMenu extends AntimodeMenu {
return true;
}
alignCenter = (state: EditorState<any>, dispatch: any) => {
- return this.alignParagraphs(state, "center", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);
}
alignLeft = (state: EditorState<any>, dispatch: any) => {
- return this.alignParagraphs(state, "left", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);
}
alignRight = (state: EditorState<any>, dispatch: any) => {
- return this.alignParagraphs(state, "right", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "right", dispatch);
}
alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
@@ -574,10 +603,11 @@ export default class RichTextMenu extends AntimodeMenu {
label = "No marks are currently stored";
}
- const button =
- <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+ 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" } : {}}>
<FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
- </button>;
+ </button>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown">
@@ -646,11 +676,12 @@ export default class RichTextMenu extends AntimodeMenu {
self.TextView.EditorView!.focus();
}
- const button =
- <button className="antimodeMenu-button color-preview-button" title="" 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}>
<FontAwesomeIcon icon="palette" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
- </button>;
+ </button>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown" >
@@ -699,11 +730,12 @@ export default class RichTextMenu extends AntimodeMenu {
UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
}
- const button =
- <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" 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}>
<FontAwesomeIcon icon="highlighter" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
- </button>;
+ </button>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown">
@@ -742,7 +774,9 @@ export default class RichTextMenu extends AntimodeMenu {
const link = this.currentLink ? this.currentLink : "";
- const button = <FontAwesomeIcon icon="link" size="lg" />;
+ const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
+ <div><FontAwesomeIcon icon="link" size="lg" /> </div>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown link-menu">
@@ -753,9 +787,7 @@ 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} />;
}
async getTextLinkTargetTitle() {
@@ -882,22 +914,22 @@ export default class RichTextMenu extends AntimodeMenu {
render() {
TraceMobx();
- const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+ const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
!this.collapsed ? this.getDragger() : (null),
- !this.Pinned ? (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" />
- ]}</>,
+ <div className="richTextMenu-divider" key="divider" />
+ ]}</div>,
this.createColorButton(),
this.createHighlighterButton(),
this.createLinkButton(),
this.createBrushButton(),
- <div className="richTextMenu-divider" />,
+ <div className="richTextMenu-divider" key="divider 2" />,
this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
@@ -907,20 +939,20 @@ export default class RichTextMenu extends AntimodeMenu {
this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
]}</div>;
- const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+ const row2 = <div className="antimodeMenu-row row-2" key="row2">
{this.collapsed ? this.getDragger() : (null)}
- <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
- <div className="richTextMenu-divider" />,
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
- this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
- <div className="richTextMenu-divider" />,
- this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"),
+ <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
+ <div className="richTextMenu-divider" key="divider 3" />,
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)),
+ this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)),
+ <div className="richTextMenu-divider" key="divider 4" />,
+ this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", action((val: string) => this.activeListType = val)),
this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
- <div className="richTextMenu-divider" />,]}
+ <div className="richTextMenu-divider" key="divider 5" />,]}
</div>
- <div key="button">
+ <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)` }} />
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ca30dde9d..ef0fead4a 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -90,7 +90,7 @@ export class RichTextRules {
textDoc.inlineTextCount = numInlines + 1;
const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" });
+ const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" });
textDocInline.title = inlineFieldKey; // give the annotation its own title
textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4e5fdbfbb..56212a773 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -637,7 +637,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onZoomWheel = (e: React.WheelEvent) => {
- if (this.active()) {
+ if (this.active(true)) {
e.stopPropagation();
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 6fd3455b6..4c0972736 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -15,6 +15,7 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
import React = require("react");
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { DocumentType } from "../../documents/DocumentTypes";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -100,7 +101,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
e.stopPropagation();
this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
- const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
+ const docs = rootTarget.type === DocumentType.COL ? DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]) :
+ DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget) + "-annotations"]);
if (this.rootDoc.presProgressivize) {
rootTarget.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 97f62c9d4..ddffb56c3 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -3,7 +3,6 @@ import { ScriptField } from "./ScriptField";
import { Doc } from "./Doc";
import { DateField } from "./DateField";
import { SchemaHeaderField } from "./SchemaHeaderField";
-import { Schema } from "prosemirror-model";
export const documentSchema = createSchema({
// content properties
@@ -55,7 +54,7 @@ export const documentSchema = createSchema({
_columnsHideIfEmpty: "boolean", // whether empty stacking view column headings should be hidden
_columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry
_schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views
- _fontSize: "number",
+ _fontSize: "string",
_fontFamily: "string",
_sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
@@ -66,6 +65,7 @@ export const documentSchema = createSchema({
color: "string", // foreground color of document
fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view
fontSize: "string",
+ hidden: "boolean", // whether a document should not be displayed
isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
layoutKey: "string", // holds the field key for the field that actually holds the current lyoat
@@ -73,6 +73,9 @@ export const documentSchema = createSchema({
opacity: "number", // opacity of document
strokeWidth: "number",
strokeBezier: "number",
+ strokeStartMarker: "string",
+ strokeEndMarker: "string",
+ strokeDash: "string",
textTransform: "string",
treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden
treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
@@ -85,6 +88,8 @@ export const documentSchema = createSchema({
onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped.
followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, )
+ hideLinkButton: "boolean", // whether the blue link counter button should be hidden
+ hideAllLinks: "boolean", // whether all individual blue anchor dots should be hidden
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/server/ApiManagers/HypothesisManager.ts b/src/server/ApiManagers/HypothesisManager.ts
new file mode 100644
index 000000000..33badbc42
--- /dev/null
+++ b/src/server/ApiManagers/HypothesisManager.ts
@@ -0,0 +1,44 @@
+import ApiManager, { Registration } from "./ApiManager";
+import { Method, _permission_denied } from "../RouteManager";
+import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
+import { Database } from "../database";
+import { writeFile, readFile, readFileSync, existsSync } from "fs";
+import { serverPathToFile, Directory } from "./UploadManager";
+
+export default class HypothesisManager extends ApiManager {
+
+ protected initialize(register: Registration): void {
+
+ register({
+ method: Method.GET,
+ subscription: "/readHypothesisAccessToken",
+ secureHandler: async ({ user, res }) => {
+ if (existsSync(serverPathToFile(Directory.hypothesis, user.id))) {
+ const read = readFileSync(serverPathToFile(Directory.hypothesis, user.id), "base64") || "";
+ console.log("READ = " + read);
+ res.send(read);
+ } else res.send("");
+ }
+ });
+
+ register({
+ method: Method.POST,
+ subscription: "/writeHypothesisAccessToken",
+ secureHandler: async ({ user, req, res }) => {
+ const write = req.body.authenticationCode;
+ console.log("WRITE = " + write);
+ res.send(await writeFile(serverPathToFile(Directory.hypothesis, user.id), write, "base64", () => { }));
+ }
+ });
+
+ register({
+ method: Method.GET,
+ subscription: "/revokeHypothesisAccessToken",
+ secureHandler: async ({ user, res }) => {
+ await Database.Auxiliary.GoogleAccessToken.Revoke("dash-hyp-" + user.id);
+ res.send();
+ }
+ });
+
+ }
+} \ No newline at end of file
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index fe39b84e6..55ceab9fb 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -24,7 +24,8 @@ export enum Directory {
pdfs = "pdfs",
text = "text",
pdf_thumbnails = "pdf_thumbnails",
- audio = "audio"
+ audio = "audio",
+ hypothesis = "hypothesis"
}
export function serverPathToFile(directory: Directory, filename: string) {
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 20f96f432..b0157a85f 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -39,7 +39,8 @@ export namespace GoogleApiServerUtils {
*/
export enum Service {
Documents = "Documents",
- Slides = "Slides"
+ Slides = "Slides",
+ Hypothesis = "Hypothesis"
}
/**
diff --git a/src/server/index.ts b/src/server/index.ts
index 083173bb5..9af4b00bc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -18,10 +18,10 @@ import PDFManager from "./ApiManagers/PDFManager";
import UploadManager from "./ApiManagers/UploadManager";
import { log_execution } from "./ActionUtilities";
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
+import HypothesisManager from "./ApiManagers/HypothesisManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
import { Logger } from "./ProcessFactory";
import { yellow } from "colors";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
import SessionManager from "./ApiManagers/SessionManager";
import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
@@ -72,6 +72,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
new DeleteManager(),
new UtilManager(),
new GeneralGoogleManager(),
+ new HypothesisManager(),
new GooglePhotosManager(),
];
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index d55c2e198..f63a35e43 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -180,7 +180,14 @@ export namespace WebSocket {
function barReceived(socket: SocketIO.Socket, userEmail: string) {
clients[userEmail] = new Client(userEmail.toString());
- console.log(green(`user ${userEmail} has connected to the web socket`));
+ const currentdate = new Date();
+ const datetime = currentdate.getDate() + "/"
+ + (currentdate.getMonth() + 1) + "/"
+ + currentdate.getFullYear() + " @ "
+ + currentdate.getHours() + ":"
+ + currentdate.getMinutes() + ":"
+ + currentdate.getSeconds();
+ console.log(green(`user ${userEmail} has connected to the web socket at: ${datetime}`));
socketMap.set(socket, userEmail);
}