aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryunahi <60233430+yunahi@users.noreply.github.com>2020-07-28 01:53:21 +0900
committeryunahi <60233430+yunahi@users.noreply.github.com>2020-07-28 01:53:21 +0900
commite8caa12dc736ef66741b5652425b0cb8b0a5eb16 (patch)
treeafd47f6b46d1d9c1d814650b2590b738cd36c495
parentf3a331573be09115f9a92b8e927081f8f743c478 (diff)
parentf869c7a75eb6a2f6e6301aa76c57e383b41d3fea (diff)
merge fix
-rw-r--r--package-lock.json71
-rw-r--r--package.json4
-rw-r--r--src/client/ClientRecommender.tsx2
-rw-r--r--src/client/DocServer.ts88
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx6
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx6
-rw-r--r--src/client/apis/IBM_Recommender.ts2
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts2
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts2
-rw-r--r--src/client/documents/Documents.ts20
-rw-r--r--src/client/util/CurrentUserUtils.ts137
-rw-r--r--src/client/util/DictationManager.ts2
-rw-r--r--src/client/util/DocumentManager.ts50
-rw-r--r--src/client/util/DragManager.ts102
-rw-r--r--src/client/util/DropConverter.ts2
-rw-r--r--src/client/util/GroupManager.scss116
-rw-r--r--src/client/util/GroupManager.tsx264
-rw-r--r--src/client/util/GroupMemberView.scss58
-rw-r--r--src/client/util/GroupMemberView.tsx49
-rw-r--r--src/client/util/InteractionUtils.tsx8
-rw-r--r--src/client/util/LinkManager.ts17
-rw-r--r--src/client/util/ScriptManager.ts10
-rw-r--r--src/client/util/Scripting.ts10
-rw-r--r--src/client/util/SearchUtil.ts1
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/util/SettingsManager.scss2
-rw-r--r--src/client/util/SettingsManager.tsx22
-rw-r--r--src/client/util/SharingManager.scss180
-rw-r--r--src/client/util/SharingManager.tsx520
-rw-r--r--src/client/views/ContextMenu.scss16
-rw-r--r--src/client/views/ContextMenuItem.tsx8
-rw-r--r--src/client/views/DictationOverlay.tsx1
-rw-r--r--src/client/views/DocComponent.tsx49
-rw-r--r--src/client/views/DocumentButtonBar.tsx107
-rw-r--r--src/client/views/DocumentDecorations.scss5
-rw-r--r--src/client/views/DocumentDecorations.tsx48
-rw-r--r--src/client/views/EditableView.tsx1
-rw-r--r--src/client/views/GestureOverlay.tsx11
-rw-r--r--src/client/views/GlobalKeyHandler.ts8
-rw-r--r--src/client/views/InkingStroke.tsx7
-rw-r--r--src/client/views/KeyphraseQueryView.tsx1
-rw-r--r--src/client/views/MainView.scss4
-rw-r--r--src/client/views/MainView.tsx29
-rw-r--r--src/client/views/MainViewModal.scss5
-rw-r--r--src/client/views/MainViewModal.tsx7
-rw-r--r--src/client/views/MetadataEntryMenu.tsx100
-rw-r--r--src/client/views/OverlayView.scss2
-rw-r--r--src/client/views/PreviewCursor.tsx4
-rw-r--r--src/client/views/ScriptBox.tsx2
-rw-r--r--src/client/views/SearchDocBox.tsx9
-rw-r--r--src/client/views/TemplateMenu.tsx7
-rw-r--r--src/client/views/Touchable.tsx3
-rw-r--r--src/client/views/animationtimeline/Keyframe.tsx4
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx1
-rw-r--r--src/client/views/animationtimeline/Track.tsx4
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx13
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx9
-rw-r--r--src/client/views/collections/CollectionDockingView.scss18
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx132
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx21
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx1
-rw-r--r--src/client/views/collections/CollectionMenu.scss (renamed from src/client/views/collections/CollectionViewChromes.scss)111
-rw-r--r--src/client/views/collections/CollectionMenu.tsx (renamed from src/client/views/collections/CollectionViewChromes.tsx)635
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx1
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx8
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx68
-rw-r--r--src/client/views/collections/CollectionSubView.tsx30
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx31
-rw-r--r--src/client/views/collections/CollectionView.tsx145
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx22
-rw-r--r--src/client/views/collections/SchemaTable.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx80
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx153
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss83
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx35
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss3
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx55
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx5
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx4
-rw-r--r--src/client/views/linking/LinkEditor.scss24
-rw-r--r--src/client/views/linking/LinkEditor.tsx41
-rw-r--r--src/client/views/linking/LinkMenu.scss9
-rw-r--r--src/client/views/linking/LinkMenu.tsx1
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx4
-rw-r--r--src/client/views/linking/LinkMenuItem.scss1
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx37
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx7
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx9
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss12
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx59
-rw-r--r--src/client/views/nodes/DocumentView.tsx138
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FontIconBox.scss3
-rw-r--r--src/client/views/nodes/FontIconBox.tsx11
-rw-r--r--src/client/views/nodes/ImageBox.tsx9
-rw-r--r--src/client/views/nodes/LinkCreatedBox.tsx31
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx4
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx12
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx1
-rw-r--r--src/client/views/nodes/RadialMenu.tsx1
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx2
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx1
-rw-r--r--src/client/views/nodes/TaskCompletedBox.scss (renamed from src/client/views/nodes/LinkCreatedBox.scss)3
-rw-r--r--src/client/views/nodes/TaskCompletedBox.tsx32
-rw-r--r--src/client/views/nodes/VideoBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.scss222
-rw-r--r--src/client/views/nodes/WebBox.tsx398
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx109
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx6
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts2
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx84
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx12
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts10
-rw-r--r--src/client/views/pdf/PDFMenu.tsx2
-rw-r--r--src/client/views/pdf/PDFViewer.tsx14
-rw-r--r--src/client/views/search/FilterBox.tsx1
-rw-r--r--src/client/views/search/SearchBox.tsx5
-rw-r--r--src/client/views/search/ToggleBar.tsx1
-rw-r--r--src/fields/Doc.ts213
-rw-r--r--src/fields/Proxy.ts8
-rw-r--r--src/fields/Schema.ts4
-rw-r--r--src/fields/ScriptField.ts22
-rw-r--r--src/fields/documentSchemas.ts4
-rw-r--r--src/fields/util.ts136
-rw-r--r--src/mobile/AudioUpload.tsx1
-rw-r--r--src/mobile/ImageUpload.tsx3
-rw-r--r--src/mobile/MobileInterface.tsx4
-rw-r--r--src/server/ApiManagers/DownloadManager.ts12
-rw-r--r--src/server/ApiManagers/UploadManager.ts16
-rw-r--r--src/server/Recommender.ts4
-rw-r--r--src/server/downsize.ts2
137 files changed, 3434 insertions, 2201 deletions
diff --git a/package-lock.json b/package-lock.json
index 1b39905cf..51105842a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -301,6 +301,21 @@
"@fortawesome/fontawesome-common-types": "^0.2.29"
}
},
+ "@fortawesome/free-brands-svg-icons": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.14.0.tgz",
+ "integrity": "sha512-WsqPFTvJFI7MYkcy0jeFE2zY+blC4OrnB9MJOcn1NxRXT/sSfEEhrI7CwzIkiYajLiVDBKWeErYOvpsMeodmCQ==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.30"
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": {
+ "version": "0.2.30",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz",
+ "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg=="
+ }
+ }
+ },
"@fortawesome/free-regular-svg-icons": {
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.1.tgz",
@@ -711,6 +726,12 @@
"express-validator": "*"
}
},
+ "@types/file-saver": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz",
+ "integrity": "sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==",
+ "dev": true
+ },
"@types/formidable": {
"version": "1.0.31",
"resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz",
@@ -6069,6 +6090,11 @@
}
}
},
+ "file-saver": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
+ "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
+ },
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -7280,6 +7306,11 @@
}
}
},
+ "immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+ },
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
@@ -8160,6 +8191,33 @@
"promise": "^7.0.1"
}
},
+ "jszip": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
+ "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
+ "requires": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "set-immediate-shim": "~1.0.1"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ }
+ }
+ },
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
@@ -8300,6 +8358,14 @@
}
}
},
+ "lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
@@ -15142,6 +15208,11 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
+ },
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
diff --git a/package.json b/package.json
index cb083020f..3b7b5f391 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"@types/express-flash": "0.0.0",
"@types/express-session": "^1.15.16",
"@types/express-validator": "^3.0.0",
+ "@types/file-saver": "^2.0.1",
"@types/formidable": "^1.0.31",
"@types/google-maps-react": "^2.0.5",
"@types/jquery": "^3.5.0",
@@ -116,6 +117,7 @@
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.29",
+ "@fortawesome/free-brands-svg-icons": "^5.14.0",
"@fortawesome/free-regular-svg-icons": "^5.13.1",
"@fortawesome/free-solid-svg-icons": "^5.13.1",
"@fortawesome/react-fontawesome": "^0.1.11",
@@ -154,6 +156,7 @@
"express-session": "^1.17.0",
"express-validator": "^5.3.1",
"expressjs": "^1.0.1",
+ "file-saver": "^2.0.2",
"find-in-files": "^0.5.0",
"fit-curve": "^0.1.7",
"flexlayout-react": "^0.3.11",
@@ -172,6 +175,7 @@
"image-size-stream": "^1.1.0",
"js-datepicker": "^4.6.6",
"jsonschema": "^1.2.5",
+ "jszip": "^3.5.0",
"libxmljs": "^0.19.7",
"lodash": "^4.17.15",
"material-ui": "^0.20.2",
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index d18669b02..3f875057e 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -50,7 +50,6 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
@observable private corr_matrix = [[0, 0], [0, 0]]; // for testing
constructor(props: RecommenderProps) {
- //console.log("creating client recommender...");
super(props);
if (!ClientRecommender.Instance) ClientRecommender.Instance = this;
ClientRecommender.Instance.docVectors = new Set();
@@ -383,7 +382,6 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
case 200:
const title_vals: string[] = [];
const url_vals: string[] = [];
- //console.log(result);
if (xml) {
const titles = xml.getElementsByTagName("title");
let counter = 1;
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 2a7a7c59a..8ded43468 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,13 +1,14 @@
import * as io from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, fetchProto } from '../fields/Doc';
+import { Opt, Doc, fetchProto, FieldsSym } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../fields/RefField';
-import { Id, HandleUpdate } from '../fields/FieldSymbols';
+import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols';
import GestureOverlay from './views/GestureOverlay';
import MobileInkOverlay from '../mobile/MobileInkOverlay';
import { runInAction } from 'mobx';
+import { ObjectField } from '../fields/ObjectField';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -31,7 +32,7 @@ export namespace DocServer {
export enum WriteMode {
Default = 0, //Anything goes
- Playground = 1, //Playground (write own/no read)
+ Playground = 1, //Playground (write own/no read other updates)
LiveReadonly = 2,//Live Readonly (no write/read others)
LivePlayground = 3,//Live Playground (write own/read others)
}
@@ -39,9 +40,9 @@ export namespace DocServer {
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
export var PlaygroundFields: string[];
- export function setPlaygroundFields(livePlayougroundFields: string[]) {
- DocServer.PlaygroundFields = livePlayougroundFields;
- livePlayougroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
+ export function setPlaygroundFields(livePlaygroundFields: string[]) {
+ DocServer.PlaygroundFields = livePlaygroundFields;
+ livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
}
export function setFieldWriteMode(field: string, writeMode: WriteMode) {
@@ -207,12 +208,12 @@ export namespace DocServer {
* the server if the document has not been cached.
* @param id the id of the requested document
*/
- const _GetRefFieldImpl = (id: string): Promise<Opt<RefField>> => {
+ const _GetRefFieldImpl = (id: string, force: boolean = false): Promise<Opt<RefField>> => {
// an initial pass through the cache to determine whether the document needs to be fetched,
// is already in the process of being fetched or already exists in the
// cache
const cached = _cache[id];
- if (cached === undefined) {
+ if (cached === undefined || force) {
// NOT CACHED => we'll have to send a request to the server
// synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
@@ -226,7 +227,16 @@ export namespace DocServer {
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
const field = await SerializationHelper.Deserialize(fieldJson);
- if (field !== undefined) {
+ if (force && field instanceof Doc && cached instanceof Doc) {
+ Array.from(Object.keys(field)).forEach(key => {
+ const fieldval = field[key];
+ if (fieldval instanceof ObjectField) {
+ fieldval[Parent] = undefined;
+ }
+ cached[key] = field[key];
+ });
+ }
+ else if (field !== undefined) {
_cache[id] = field;
} else {
delete _cache[id];
@@ -238,8 +248,8 @@ export namespace DocServer {
});
// here, indicate that the document associated with this id is currently
// being retrieved and cached
- _cache[id] = deserializeField;
- return deserializeField;
+ !force && (_cache[id] = deserializeField);
+ return force ? cached as any : deserializeField;
} else if (cached instanceof Promise) {
// BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
// and requested the document I'm looking for. Shouldn't fetch again, just
@@ -260,11 +270,11 @@ export namespace DocServer {
}
};
- let _GetRefField: (id: string) => Promise<Opt<RefField>> = errorFunc;
+ let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
let _GetCachedRefField: (id: string) => Opt<RefField> = errorFunc;
- export function GetRefField(id: string): Promise<Opt<RefField>> {
- return _GetRefField(id);
+ export function GetRefField(id: string, force = false): Promise<Opt<RefField>> {
+ return _GetRefField(id, force);
}
export function GetCachedRefField(id: string): Opt<RefField> {
return _GetCachedRefField(id);
@@ -330,29 +340,35 @@ export namespace DocServer {
const proms: Promise<void>[] = [];
runInAction(() => {
for (const field of fields) {
- if (field !== undefined && field !== null) {
+ if (field !== undefined && field !== null && !_cache[field.id]) {
// deserialize
- const prom = SerializationHelper.Deserialize(field).then(deserialized => {
- fieldMap[field.id] = deserialized;
-
- //overwrite or delete any promises (that we inserted as flags
- // to indicate that the field was in the process of being fetched). Now everything
- // should be an actual value within or entirely absent from the cache.
- if (deserialized !== undefined) {
- _cache[field.id] = deserialized;
- } else {
- delete _cache[field.id];
- }
- return deserialized;
- });
- // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
- // we set the value at the field's id to a promise that will resolve to the field.
- // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
- // The mapping in the .then call ensures that when other callers await these promises, they'll
- // get the resolved field
- _cache[field.id] = prom;
- // adds to a list of promises that will be awaited asynchronously
- proms.push(prom);
+ const cached = _cache[field.id];
+ if (!cached) {
+ const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+ fieldMap[field.id] = deserialized;
+
+ //overwrite or delete any promises (that we inserted as flags
+ // to indicate that the field was in the process of being fetched). Now everything
+ // should be an actual value within or entirely absent from the cache.
+ if (deserialized !== undefined) {
+ _cache[field.id] = deserialized;
+ } else {
+ delete _cache[field.id];
+ }
+ return deserialized;
+ });
+ // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+ // we set the value at the field's id to a promise that will resolve to the field.
+ // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+ // The mapping in the .then call ensures that when other callers await these promises, they'll
+ // get the resolved field
+ _cache[field.id] = prom;
+
+ // adds to a list of promises that will be awaited asynchronously
+ proms.push(prom);
+ } else if (cached instanceof Promise) {
+ proms.push(cached as any);
+ }
}
}
});
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index bf4469aeb..117d1fa1e 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -146,7 +146,7 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
private get dialogueBoxStyle() {
const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
- return { borderColor, transition: "0.2s borderColor ease" };
+ return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
}
render() {
@@ -155,8 +155,10 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
isDisplayed={this.openState}
interactive={true}
contents={this.renderPrompt}
- overlayDisplayedOpacity={0.9}
+ // overlayDisplayedOpacity={0.9}
dialogueBoxStyle={this.dialogueBoxStyle}
+ overlayStyle={{ zIndex: 1001 }}
+ closeOnExternalClick={action(() => this.isOpen = false)}
/>
);
}
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
index a7fcf86a4..c3e8d2fff 100644
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -138,7 +138,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
private get dialogueBoxStyle() {
const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
- return { borderColor, transition: "0.2s borderColor ease" };
+ return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
}
render() {
@@ -147,8 +147,10 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
isDisplayed={this.openState}
interactive={true}
contents={this.renderPrompt}
- overlayDisplayedOpacity={0.9}
+ // overlayDisplayedOpacity={0.9}
dialogueBoxStyle={this.dialogueBoxStyle}
+ overlayStyle={{ zIndex: 1001 }}
+ closeOnExternalClick={action(() => this.isOpen = false)}
/>
);
}
diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts
index 480b9cb1c..e6265fcb5 100644
--- a/src/client/apis/IBM_Recommender.ts
+++ b/src/client/apis/IBM_Recommender.ts
@@ -29,10 +29,8 @@
// export const analyze = async (_parameters: any): Promise<Opt<string>> => {
// try {
// const response = await naturalLanguageUnderstanding.analyze(_parameters);
-// console.log(response);
// return (JSON.stringify(response, null, 2));
// } catch (err) {
-// console.log('error: ', err);
// return undefined;
// }
// };
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 13bfb3a91..92eaf2e73 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -156,8 +156,6 @@ export namespace GooglePhotos {
const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE);
for (const value of values) {
const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id);
- console.log("Searching " + value);
- console.log(searched);
searched?.forEach(async id => {
const image = await Cast(idMapping[id], Doc);
if (image) {
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 6b0b3e029..80961af14 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -377,7 +377,6 @@ export namespace CognitiveServices {
console.log("successful vectorization!");
const vectorValues = new List<number>();
indices.forEach((ind: any) => {
- //console.log(wordvec.word);
vectorValues.push(wordvecs[ind]);
});
ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
@@ -385,7 +384,6 @@ export namespace CognitiveServices {
else {
console.log("unsuccessful :( word(s) not in vocabulary");
}
- //console.log(vectorValues.size);
}
);
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a415e17c8..cd2792226 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -69,6 +69,7 @@ export interface DocumentOptions {
_showTitle?: string; // which field to display in the title area. leave empty to have no title
_showCaption?: string; // which field to display in the caption area. leave empty to have no caption
_scrollTop?: number; // scroll location for pdfs
+ _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged
_chromeStatus?: string;
_viewType?: string; // sub type of a collection
_gridGap?: number; // gap between items in masonry view
@@ -91,7 +92,8 @@ export interface DocumentOptions {
layoutKey?: string;
type?: string;
title?: string;
- label?: string; // short form of title for use as an icon label
+ label?: string;
+ toolTip?: string; // tooltip to display on hover
style?: string;
page?: number;
description?: string; // added for links
@@ -102,6 +104,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;
@@ -136,6 +140,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
@@ -489,7 +495,7 @@ export namespace Docs {
Doc.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
Doc.AddDocToList(parentProto, "data", doc);
} else if (errors) {
- console.log(errors);
+ console.log("Documents:" + errors);
} else {
alert("A Buxton document import was completely empty (??)");
}
@@ -545,6 +551,11 @@ export namespace Docs {
const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
+ // so that the list of annotations is already initialised, prevents issues in addonly.
+ // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
+
+ dataDoc[fieldKey + "-annotations"] = new List<Doc>();
+
proto.links = ComputedField.MakeFunction("links(self)");
viewDoc.author = Doc.CurrentUserEmail;
@@ -777,7 +788,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) {
@@ -910,6 +921,8 @@ export namespace DocUtils {
if (target.doc === Doc.UserDoc()) return undefined;
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');
@@ -973,6 +986,7 @@ export namespace DocUtils {
}
if (type.indexOf("pdf") !== -1) {
ctor = Docs.Create.PdfDocument;
+ if (!options._fitWidth) options._fitWidth = true;
if (!options._width) options._width = 400;
if (!options._height) options._height = options._width * 1200 / 927;
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7a06e1bc1..6d752832a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -4,7 +4,7 @@ import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
import { UndoManager } from "./UndoManager";
-import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, DataSym } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { ScriptField, ComputedField } from "../../fields/ScriptField";
@@ -22,6 +22,7 @@ import { DocumentType } from "../documents/DocumentTypes";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
import { LabelBox } from "../views/nodes/LabelBox";
+import { LinkManager } from "./LinkManager";
export class CurrentUserUtils {
private static curr_id: string;
@@ -91,29 +92,29 @@ export class CurrentUserUtils {
}
if (doc["template-button-description"] === undefined) {
- const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created
- Doc.GetProto(descriptionTemplate).layout =
+ const descriptionTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header")); // text needs to be a space to allow templateText to be created
+ descriptionTemplate[DataSym].layout =
"<div>" +
" <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
" <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
"</div>";
- descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView");
+ (descriptionTemplate.proto as Doc).isTemplateDoc = makeTemplate(descriptionTemplate.proto as Doc, true, "descriptionView");
doc["template-button-description"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize"
});
}
if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to.
- const linkTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created
+ const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header")); // text needs to be a space to allow templateText to be created
Doc.GetProto(linkTemplate).layout =
"<div>" +
" <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" +
" <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
"</div>";
- linkTemplate.isTemplateDoc = makeTemplate(linkTemplate, true, "linkView");
+ (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView");
const rtf2 = {
doc: {
@@ -146,7 +147,7 @@ export class CurrentUserUtils {
linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
doc["template-button-link"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
dragFactory: new PrefetchProxy(linkTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "link view", icon: "window-maximize"
});
@@ -345,6 +346,14 @@ export class CurrentUserUtils {
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
}
+ if (doc["template-icon-view-button"] === undefined) {
+ const iconBtnView = Docs.Create.FontIconDocument({
+ title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
+ _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+ });
+ iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
+ doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
+ }
if (doc["template-icon-view-img"] === undefined) {
const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
@@ -358,11 +367,11 @@ export class CurrentUserUtils {
doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
}
if (doc["template-icons"] === undefined) {
- doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+ doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 }));
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
- const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+ const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
await Promise.all(curIcons!);
@@ -373,7 +382,7 @@ export class CurrentUserUtils {
}
static creatorBtnDescriptors(doc: Doc): {
- title: string, label: string, icon: string, drag?: string, ignoreClick?: boolean,
+ title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean,
click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc
}[] {
if (doc.emptyPresentation === undefined) {
@@ -390,6 +399,21 @@ export class CurrentUserUtils {
if (doc.emptyScript === undefined) {
doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" });
}
+ if (doc.emptyScreenshot === undefined) {
+ doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" });
+ }
+ if (doc.emptyAudio === undefined) {
+ doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio" });
+ }
+ if (doc.emptyImage === undefined) {
+ doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat" });
+ }
+ if (doc.emptyButton === undefined) {
+ doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button" });
+ }
+ if (doc.emptySearch === undefined) {
+ doc.emptySearch = Docs.Create.QueryDocument({ _width: 200, title: "empty search" });
+ }
if (doc.emptyDocHolder === undefined) {
doc.emptyDocHolder = Docs.Create.DocumentDocument(
ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any,
@@ -402,27 +426,28 @@ export class CurrentUserUtils {
this.setupActiveMobileMenu(doc);
}
return [
- { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
- { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
- { 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 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 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()' },
+ { toolTip: "Drag a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
+ { toolTip: "Drag a web page", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
+ { toolTip: "Drag a cat image", title: "Image", icon: "cat", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyImage as Doc },
+ { toolTip: "Drag a comparison box", title: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
+ { toolTip: "Drag a screengrabber", title: "Grab", icon: "photo-video", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScreenshot as Doc },
+ // { title: "Drag a webcam", title: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
+ { toolTip: "Drag a audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc },
+ { toolTip: "Drag a button", title: "Button", icon: "bolt", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyButton as Doc },
+
+ { toolTip: "Drag a presentation view", title: "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 },
+ { toolTip: "Drag a search box", title: "Query", icon: "search", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptySearch as Doc },
+ { toolTip: "Drag a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
+ // { title: "Drag an import folder", title: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
+ { toolTip: "Drag a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
+ // { title: "Drag an instance of the device collection", title: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
// { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
// { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
- { 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" })' },
+ { toolTip: "Drag a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyDocHolder as Doc },
+ { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
];
}
@@ -439,11 +464,11 @@ export class CurrentUserUtils {
}
}
const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
- const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
+ const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
_nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
icon,
title,
- label,
+ toolTip,
ignoreClick,
dropAction: "copy",
onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
@@ -470,7 +495,6 @@ export class CurrentUserUtils {
// Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
static setupActiveMobileMenu(doc: Doc) {
if (doc.activeMobileMenu === undefined) {
- console.log("undefined");
doc.activeMobileMenu = this.setupMobileMenu();
}
return doc.activeMobileMenu as Doc;
@@ -599,7 +623,7 @@ export class CurrentUserUtils {
_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)'),
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
dragFactory: toolsStack,
removeDropProperties: new List<string>(["lockedPosition"]),
stayInCollection: true,
@@ -664,12 +688,12 @@ export class CurrentUserUtils {
_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)'),
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
dragFactory: libraryStack,
removeDropProperties: new List<string>(["lockedPosition"]),
stayInCollection: true,
targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
- onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
+ onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel")
}));
}
return doc["tabs-button-library"] as Doc;
@@ -719,33 +743,25 @@ export class CurrentUserUtils {
}
static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
- ...opts,
- _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", forceActive: true,
+ ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", forceActive: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
backgroundColor: "black", treeViewPreventOpen: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true
})) as any as Doc
static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
- ...opts,
- dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100
+ ...opts, dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100
})) as any as Doc
/// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
static setupDockedButtons(doc: Doc) {
- if (doc["dockedBtn-pen"] === undefined) {
- doc["dockedBtn-pen"] = CurrentUserUtils.ficon({
- onClick: ScriptField.MakeScript("activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)"),
- author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activeInkPen, this)`), activeInkPen: doc
- });
- }
if (doc["dockedBtn-undo"] === undefined) {
- doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
+ doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), toolTip: "click to undo", title: "undo", icon: "undo-alt" });
}
if (doc["dockedBtn-redo"] === undefined) {
- doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
+ doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), toolTip: "click to redo", title: "redo", icon: "redo-alt" });
}
if (doc.dockedBtns === undefined) {
- doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc, doc["dockedBtn-pen"] as Doc]);
+ doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
}
}
// sets up the default set of documents to be shown in the Overlay layer
@@ -775,17 +791,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" });
}
@@ -797,14 +813,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));
@@ -872,3 +896,8 @@ export class CurrentUserUtils {
Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); },
"creates a new workspace when called");
+
+Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
+ "returns all the links to the document or its annotations", "(doc: any)");
+Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); },
+ "returns all the links directly to the document", "(doc: any)");
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 28b1ca6cf..540540642 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -121,7 +121,7 @@ export namespace DictationManager {
const listenImpl = (options?: Partial<ListeningOptions>) => {
if (!recognizer) {
- console.log(unsupported);
+ console.log("DictationManager:" + unsupported);
return unsupported;
}
if (isListening) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 1fa5faeb3..523dbfca0 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -9,6 +9,7 @@ import { LinkManager } from './LinkManager';
import { Scripting } from './Scripting';
import { SelectionManager } from './SelectionManager';
import { DocumentType } from '../documents/DocumentTypes';
+import { TraceMobx } from '../../fields/util';
export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
@@ -104,8 +105,9 @@ export class DocumentManager {
@computed
public get LinkedDocumentViews() {
+ TraceMobx();
const pairs = DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
- const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
+ const linksList = DocListCast(dv.props.Document.links);
pairs.push(...linksList.reduce((pairs, link) => {
const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
@@ -146,22 +148,22 @@ 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;
- if (docView) {
- docView.props.focus(annotatedDoc, false);
- }
+ docView?.props.focus(annotatedDoc, false);
}
}
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);
+ highlight();
}
- highlight();
} else {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
@@ -216,25 +218,27 @@ export class DocumentManager {
const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
- const linkDoc = linkDocList.length && linkDocList[0];
- if (linkDoc) {
- const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
- (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
- const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
- doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
- (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
- if (target) {
- const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
- containerDoc.currentTimecode = targetTimecode;
- const targetContext = await target?.context as Doc;
- const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+ followLinks.forEach(async linkDoc => {
+ if (linkDoc) {
+ const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
+ (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+ const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
+ doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
+ (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
+ if (target) {
+ const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
+ containerDoc.currentTimecode = targetTimecode;
+ const targetContext = await target?.context as Doc;
+ const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ } else {
+ finished?.();
+ }
} else {
finished?.();
}
- } else {
- finished?.();
- }
+ });
}
}
Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 5f34509a1..4b1860b5c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -7,7 +7,7 @@ import { listSpec } from "../../fields/Schema";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types";
-import { emptyFunction } from "../../Utils";
+import { emptyFunction, returnTrue } from "../../Utils";
import { Docs, DocUtils } from "../documents/Documents";
import * as globalCssVariables from "../views/globalCssVariables.scss";
import { UndoManager } from "./UndoManager";
@@ -83,6 +83,7 @@ export namespace DragManager {
hideSource?: boolean; // hide source document during drag
offsetX?: number; // offset of top left of source drag visual from cursor
offsetY?: number;
+ noAutoscroll?: boolean;
}
// event called when the drag operation results in a drop action
@@ -223,15 +224,18 @@ export namespace DragManager {
}
// drag a button template and drop a new button
- export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ export function
+ StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
- const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
+ const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
return e;
};
+ options = options ?? {};
+ options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
}
@@ -407,6 +411,7 @@ export namespace DragManager {
const yFromTop = downY - elesCont.top;
const xFromRight = elesCont.right - downX;
const yFromBottom = elesCont.bottom - downY;
+ let scrollAwaiter: Opt<NodeJS.Timeout>;
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
@@ -429,50 +434,55 @@ export namespace DragManager {
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,
+ if (target && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) {
+ const autoScrollHandler = () => {
+ target.dispatchEvent(
+ new CustomEvent<React.DragEvent>("dashDragAutoScroll", {
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"
- }
- })
- );
+ 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("dashDragAutoScroll"),
+ currentTarget: target,
+ target: target,
+ bubbles: true,
+ cancelable: true,
+ defaultPrevented: true,
+ eventPhase: e.eventPhase,
+ isTrusted: true,
+ preventDefault: () => "not implemented for this event" ? false : false,
+ isDefaultPrevented: () => "not implemented for this event" ? false : false,
+ stopPropagation: () => "not implemented for this event" ? false : false,
+ isPropagationStopped: () => "not implemented for this event" ? false : false,
+ persist: emptyFunction,
+ timeStamp: e.timeStamp,
+ type: "dashDragAutoScroll"
+ }
+ })
+ );
+
+ scrollAwaiter && clearTimeout(scrollAwaiter);
+ SnappingManager.GetIsDragging() && (scrollAwaiter = setTimeout(autoScrollHandler, 25));
+ };
+ scrollAwaiter && clearTimeout(scrollAwaiter);
+ scrollAwaiter = setTimeout(autoScrollHandler, 250);
}
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f9837298d..d0acf14c3 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -57,7 +57,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
data?.draggedDocuments.map((doc, i) => {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
- if (doc.type === DocumentType.FONTICON) {
+ if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) {
dbox = Doc.MakeAlias(doc);
} else if (!doc.onDragStart && !doc.isButtonBar) {
const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 544a79e98..51e4fa9e2 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,23 +1,47 @@
-@import "../views/globalCssVariables";
-
.group-interface {
- background-color: whitesmoke !important;
- color: grey;
- width: 450px;
+ width: 380px;
height: 300px;
.dialogue-box {
- width: 450;
- height: 300;
+ .group-create {
+ display: flex;
+ flex-direction: column;
+ height: 90%;
+ justify-content: space-between;
+ margin-left: 5px;
+
+ input {
+ border-radius: 5px;
+ padding: 8px;
+ min-width: 100%;
+ border: 1px solid hsl(0, 0%, 80%);
+ outline: none;
+ height: 30;
+
+ &:focus {
+ border: 2.5px solid #2684FF;
+ }
+ }
+
+ p {
+ font-size: 20px;
+ text-align: left;
+ color: black;
+ }
+
+ button {
+ align-self: flex-end;
+ }
+ }
}
+
button {
- background: $lighter-alt-accent;
+ align-self: center;
outline: none;
border-radius: 5px;
border: 0px;
- color: #fcfbf7;
- text-transform: uppercase;
+ text-transform: none;
letter-spacing: 2px;
font-size: 75%;
padding: 10px;
@@ -36,12 +60,6 @@
border-radius: 10px;
}
- button {
- width: 100%;
- align-self: center;
- background: $darker-alt-accent;
- }
-
.delete-button {
background: rgb(227, 86, 86);
}
@@ -55,33 +73,33 @@
}
.group-heading {
- letter-spacing: .5em;
- }
-
-
- .group-body {
display: flex;
- justify-content: space-between;
- max-height: 80%;
+ align-items: center;
+ margin-bottom: 25px;
- .group-create {
- display: flex;
- flex-direction: column;
- flex-basis: 30%;
- margin-left: 5px;
+ p {
+ font-size: 20px;
+ text-align: left;
+ margin-right: 15px;
+ color: black;
+ }
+ }
- input {
- border-radius: 5px;
- border: none;
- padding: 4px;
- min-width: 100%;
- margin: 4px 0 4px 0;
- }
+ .main-container {
+ display: flex;
+ flex-direction: column;
+ .sort-groups {
+ text-align: left;
+ margin-left: 5;
+ cursor: pointer;
}
- .group-content {
- padding-left: 1em;
+ .group-body {
+ justify-content: space-between;
+ height: 220;
+ background-color: #e8e8e8;
+
padding-right: 1em;
justify-content: space-around;
text-align: left;
@@ -91,17 +109,18 @@
.group-row {
display: flex;
- position: relative;
margin-bottom: 5px;
- min-height: 40px;
- border: 1px solid;
- border-radius: 10px;
+ min-height: 30px;
align-items: center;
.group-name {
- position: relative;
max-width: 65%;
- left: 10;
+ margin: 0 10;
+ color: black;
+ }
+
+ .group-info {
+ cursor: pointer;
}
button {
@@ -112,10 +131,6 @@
}
}
- ::placeholder {
- color: $intermediate-color;
- }
-
input {
border-radius: 5px;
border: none;
@@ -126,11 +141,4 @@
}
}
-
- h1 {
- color: $dark-color;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 120%;
- }
} \ No newline at end of file
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 7c68fc2a0..72fba5c1b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -3,7 +3,7 @@ import { observable, action, runInAction, computed } from "mobx";
import { SelectionManager } from "./SelectionManager";
import MainViewModal from "../views/MainViewModal";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../fields/Doc";
+import { Doc, DocListCast, Opt, DocListCastAsync } from "../../fields/Doc";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as fa from '@fortawesome/free-solid-svg-icons';
import { library } from "@fortawesome/fontawesome-svg-core";
@@ -12,10 +12,13 @@ import { Utils } from "../../Utils";
import * as RequestPromise from "request-promise";
import Select from 'react-select';
import "./GroupManager.scss";
-import { StrCast } from "../../fields/Types";
+import { StrCast, Cast } from "../../fields/Types";
import GroupMemberView from "./GroupMemberView";
+import { setGroups } from "../../fields/util";
+import { DocServer } from "../DocServer";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-library.add(fa.faWindowClose);
+library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle);
export interface UserOptions {
label: string;
@@ -32,26 +35,58 @@ export default class GroupManager extends React.Component<{}> {
@observable private users: string[] = []; // list of users populated from the database.
@observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
@observable currentGroup: Opt<Doc>; // the currently selected group.
+ @observable private createGroupModalOpen: boolean = false;
private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
+ private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
+ private currentUserGroups: string[] = [];
+ @observable private buttonColour: "#979797" | "black" = "#979797";
+ @observable private groupSort: "ascending" | "descending" | "none" = "none";
+
+
constructor(props: Readonly<{}>) {
super(props);
GroupManager.Instance = this;
}
- // sets up the list of users
componentDidMount() {
- this.populateUsers().then(resolved => runInAction(() => this.users = resolved));
+ this.populateUsers();
+ this.populateGroups();
}
/**
- * Fetches the list of users stored on the database and @returns a list of the emails.
+ * Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- const userList: User[] = JSON.parse(await RequestPromise.get(Utils.prepend("/getUsers")));
- const currentUserIndex = userList.findIndex(user => user.email === Doc.CurrentUserEmail);
- currentUserIndex !== -1 && userList.splice(currentUserIndex, 1);
- return userList.map(user => user.email);
+ runInAction(() => this.users = []);
+ const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const raw = JSON.parse(userList) as User[];
+ const evaluating = raw.map(async user => {
+ // const isCandidate = user.email !== Doc.CurrentUserEmail;
+ // if (isCandidate) {
+ const userDocument = await DocServer.GetRefField(user.userDocumentId);
+ if (userDocument instanceof Doc) {
+ const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc);
+ runInAction(() => {
+ if (notificationDoc instanceof Doc) {
+ this.users.push(user.email);
+ }
+ });
+ }
+ // }
+ });
+ return Promise.all(evaluating);
+ }
+
+ populateGroups = () => {
+ DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
+ groups?.forEach(group => {
+ const members: string[] = JSON.parse(StrCast(group.members));
+ if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
+ });
+
+ setGroups(this.currentUserGroups);
+ });
}
/**
@@ -68,6 +103,8 @@ export default class GroupManager extends React.Component<{}> {
open = () => {
SelectionManager.DeselectAll();
this.isOpen = true;
+ this.populateUsers();
+ this.populateGroups();
}
/**
@@ -77,6 +114,8 @@ export default class GroupManager extends React.Component<{}> {
close = () => {
this.isOpen = false;
this.currentGroup = undefined;
+ // this.users = [];
+ this.createGroupModalOpen = false;
}
/**
@@ -89,7 +128,8 @@ export default class GroupManager extends React.Component<{}> {
/**
* @returns a list of all group documents.
*/
- private getAllGroups(): Doc[] {
+ // private ?
+ getAllGroups(): Doc[] {
const groupDoc = this.GroupManagerDoc;
return groupDoc ? DocListCast(groupDoc.data) : [];
}
@@ -98,7 +138,8 @@ export default class GroupManager extends React.Component<{}> {
* @returns a group document based on the group name.
* @param groupName
*/
- private getGroup(groupName: string): Doc | undefined {
+ // private?
+ getGroup(groupName: string): Doc | undefined {
const groupDoc = this.getAllGroups().find(group => group.groupName === groupName);
return groupDoc;
}
@@ -123,6 +164,11 @@ export default class GroupManager extends React.Component<{}> {
);
}
+ getGroupMembers(group: string | Doc): string[] {
+ if (group instanceof Doc) return JSON.parse(StrCast(group.members)) as string[];
+ else return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
+ }
+
/**
* @returns the members of the admin group.
*/
@@ -150,6 +196,10 @@ export default class GroupManager extends React.Component<{}> {
groupDoc.groupName = groupName;
groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
groupDoc.members = JSON.stringify(memberEmails);
+ if (memberEmails.includes(Doc.CurrentUserEmail)) {
+ this.currentUserGroups.push(groupName);
+ setGroups(this.currentUserGroups);
+ }
this.addGroup(groupDoc);
}
@@ -172,8 +222,16 @@ export default class GroupManager extends React.Component<{}> {
deleteGroup(group: Doc): boolean {
if (group) {
if (this.GroupManagerDoc && this.hasEditAccess(group)) {
+ // TODO look at this later
+ // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
- SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
+ SharingManager.Instance.removeGroup(group);
+ const members: string[] = JSON.parse(StrCast(group.members));
+ if (members.includes(Doc.CurrentUserEmail)) {
+ const index = this.currentUserGroups.findIndex(groupName => groupName === group.groupName);
+ index !== -1 && this.currentUserGroups.splice(index, 1);
+ setGroups(this.currentUserGroups);
+ }
if (group === this.currentGroup) {
runInAction(() => this.currentGroup = undefined);
}
@@ -193,6 +251,7 @@ export default class GroupManager extends React.Component<{}> {
const memberList: string[] = JSON.parse(StrCast(groupDoc.members));
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
+ SharingManager.Instance.shareWithAddedMember(groupDoc, email);
}
}
@@ -205,8 +264,11 @@ export default class GroupManager extends React.Component<{}> {
if (this.hasEditAccess(groupDoc)) {
const memberList: string[] = JSON.parse(StrCast(groupDoc.members));
const index = memberList.indexOf(email);
- index !== -1 && memberList.splice(index, 1);
- groupDoc.members = JSON.stringify(memberList);
+ if (index !== -1) {
+ const user = memberList.splice(index, 1)[0];
+ groupDoc.members = JSON.stringify(memberList);
+ SharingManager.Instance.removeMember(groupDoc, email);
+ }
}
}
@@ -243,104 +305,133 @@ export default class GroupManager extends React.Component<{}> {
this.createGroupDoc(this.inputRef.current.value, this.selectedUsers?.map(user => user.value));
this.selectedUsers = null;
this.inputRef.current.value = "";
+ this.buttonColour = "#979797";
+
+ const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect();
+ TaskCompletionBox.popupX = left - 2 * width;
+ TaskCompletionBox.popupY = top;
+ TaskCompletionBox.textDisplayed = "Group created!";
+ TaskCompletionBox.taskCompleted = true;
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
}
- /**
- * A getter that @returns the interface rendered to view an individual group.
- */
- private get editingInterface() {
- const members: string[] = this.currentGroup ? JSON.parse(StrCast(this.currentGroup.members)) : [];
- const options: UserOptions[] = this.currentGroup ? this.options.filter(option => !(JSON.parse(StrCast(this.currentGroup!.members)) as string[]).includes(option.value)) : [];
- return (!this.currentGroup ? null :
- <div className="editing-interface">
- <div className="editing-header">
- <b>{this.currentGroup.groupName}</b>
- <div className={"close-button"} onClick={action(() => this.currentGroup = undefined)}>
- <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+ private get groupCreationModal() {
+ const contents = (
+ <div className="group-create">
+ <div className="group-heading" style={{ marginBottom: 0 }}>
+ <p><b>New Group</b></p>
+ <div className={"close-button"} onClick={action(() => this.createGroupModalOpen = false)}>
+ <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
</div>
-
- {this.hasEditAccess(this.currentGroup) ?
- <div className="group-buttons">
- <div className="add-member-dropdown">
- <Select
- // isMulti={true}
- isSearchable={true}
- options={options}
- onChange={selectedOption => this.addMemberToGroup(this.currentGroup!, (selectedOption as UserOptions).value)}
- placeholder={"Add members"}
- value={null}
- closeMenuOnSelect={true}
- />
- </div>
- <button onClick={() => this.deleteGroup(this.currentGroup!)}>Delete group</button>
- </div> :
- null}
- </div>
- <div className="editing-contents">
- {members.map(member => (
- <div className="editing-row">
- <div className="user-email">
- {member}
- </div>
- {this.hasEditAccess(this.currentGroup!) ? <button onClick={() => this.removeMemberFromGroup(this.currentGroup!, member)}> Remove </button> : null}
- </div>
- ))}
</div>
+ <input
+ className="group-input"
+ ref={this.inputRef}
+ onKeyDown={this.handleKeyDown}
+ type="text"
+ placeholder="Group name"
+ onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} />
+ <Select
+ isMulti={true}
+ isSearchable={true}
+ options={this.options}
+ onChange={this.handleChange}
+ placeholder={"Select users"}
+ value={this.selectedUsers}
+ closeMenuOnSelect={false}
+ styles={{
+ dropdownIndicator: (base, state) => ({
+ ...base,
+ transition: '0.5s all ease',
+ transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+ }),
+ multiValue: (base) => ({
+ ...base,
+ maxWidth: "50%",
+
+ '&:hover': {
+ maxWidth: "unset"
+ }
+ })
+ }}
+ />
+ <button
+ ref={this.createGroupButtonRef}
+ onClick={this.createGroup}
+ style={{ background: this.buttonColour }}
+ disabled={this.buttonColour === "#979797"}
+ >
+ Create
+ </button>
</div>
);
+ return (
+ <MainViewModal
+ isDisplayed={this.createGroupModalOpen}
+ interactive={true}
+ contents={contents}
+ dialogueBoxStyle={{ width: "90%", height: "70%" }}
+ closeOnExternalClick={action(() => this.createGroupModalOpen = false)}
+ />
+ );
}
/**
* A getter that @returns the main interface for the GroupManager.
*/
private get groupInterface() {
+
+ const sortGroups = (d1: Doc, d2: Doc) => {
+ const g1 = StrCast(d1.groupName);
+ const g2 = StrCast(d2.groupName);
+
+ return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
+ };
+
+ let groups = this.getAllGroups();
+ groups = this.groupSort === "ascending" ? groups.sort(sortGroups) : this.groupSort === "descending" ? groups.sort(sortGroups).reverse() : groups;
+
return (
<div className="group-interface">
- {/* <MainViewModal
- contents={this.editingInterface}
- isDisplayed={this.currentGroup ? true : false}
- interactive={true}
- dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
- overlayDisplayedOpacity={this.overlayOpacity}
- /> */}
+ {this.groupCreationModal}
{this.currentGroup ?
<GroupMemberView
group={this.currentGroup}
- onCloseButtonClick={() => this.currentGroup = undefined}
+ onCloseButtonClick={action(() => this.currentGroup = undefined)}
/>
: null}
<div className="group-heading">
- <h1>Groups</h1>
+ <p><b>Manage Groups</b></p>
+ <button onClick={action(() => this.createGroupModalOpen = true)}>
+ <FontAwesomeIcon icon={fa.faPlus} size={"sm"} /> Create Group
+ </button>
<div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+ <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
</div>
</div>
- <div className="group-body">
- <div className="group-create">
- <button onClick={this.createGroup}>Create group</button>
- <input ref={this.inputRef} onKeyDown={this.handleKeyDown} type="text" placeholder="Group name" />
- <Select
- isMulti={true}
- isSearchable={true}
- options={this.options}
- onChange={this.handleChange}
- placeholder={"Select users"}
- value={this.selectedUsers}
- closeMenuOnSelect={false}
- />
+ <div className="main-container">
+ <div
+ className="sort-groups"
+ onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
+ Name {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
</div>
- <div className="group-content">
- {this.getAllGroups().map(group =>
- <div className="group-row">
- <div className="group-name">{group.groupName}</div>
- <button onClick={action(() => this.currentGroup = group)}>
- {this.hasEditAccess(group) ? "Edit" : "View"}
- </button>
+ <div className="group-body">
+ {groups.map(group =>
+ <div
+ className="group-row"
+ key={StrCast(group.groupName)}
+ >
+ <div className="group-name" >{group.groupName}</div>
+ <div className="group-info" onClick={action(() => this.currentGroup = group)}>
+ <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+ </div>
</div>
)}
</div>
</div>
+
</div>
);
}
@@ -351,8 +442,9 @@ export default class GroupManager extends React.Component<{}> {
contents={this.groupInterface}
isDisplayed={this.isOpen}
interactive={true}
- dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
- overlayDisplayedOpacity={this.overlayOpacity}
+ dialogueBoxStyle={{ zIndex: 1002 }}
+ overlayStyle={{ zIndex: 1001 }}
+ closeOnExternalClick={this.close}
/>
);
}
diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss
index 7833c485f..c609c5c7b 100644
--- a/src/client/util/GroupMemberView.scss
+++ b/src/client/util/GroupMemberView.scss
@@ -1,18 +1,17 @@
-@import "../views/globalCssVariables";
-
.editing-interface {
- background-color: whitesmoke !important;
- color: grey;
width: 100%;
height: 100%;
+ hr {
+ margin-top: 20;
+ }
+
button {
- background: $darker-alt-accent;
outline: none;
border-radius: 5px;
border: 0px;
color: #fcfbf7;
- text-transform: uppercase;
+ text-transform: none;
letter-spacing: 2px;
font-size: 75%;
padding: 10px;
@@ -32,34 +31,69 @@
.editing-header {
margin-bottom: 5;
+ .group-title {
+ font-weight: bold;
+ font-size: 15;
+ text-align: center;
+ border: none;
+ outline: none;
+ color: black;
+ margin-top: -5;
+ height: 20;
+ text-overflow: ellipsis;
+
+ &:hover {
+ text-overflow: visible;
+ overflow-x: auto;
+ }
+ }
+
+ .sort-emails {
+ float: left;
+ margin: -18 0 0 5;
+ cursor: pointer;
+ }
+
.group-buttons {
display: flex;
margin-top: 5;
+ margin-bottom: 25;
.add-member-dropdown {
- width: 100%;
+ width: 65%;
margin: 0 5;
+
+ input {
+ height: 30;
+ }
}
}
}
.editing-contents {
overflow-y: auto;
- // max-height: 67%;
- height: 67%;
+ height: 65%;
width: 100%;
+ color: black;
+ margin-top: -15px;
.editing-row {
display: flex;
align-items: center;
- // border: 1px solid;
- // border-radius: 10px;
+ margin-bottom: 10px;
+ position: relative;
.user-email {
- // position: relative;
min-width: 65%;
word-break: break-all;
padding: 0 5;
+ text-align: left;
+ }
+
+ .remove-button {
+ position: absolute;
+ right: 10;
+ cursor: pointer;
}
}
}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index b2d75158e..f20670c4e 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -4,14 +4,14 @@ import { observer } from "mobx-react";
import GroupManager, { UserOptions } from "./GroupManager";
import { library } from "@fortawesome/fontawesome-svg-core";
import { StrCast } from "../../fields/Types";
-import { action } from "mobx";
+import { action, observable } from "mobx";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as fa from '@fortawesome/free-solid-svg-icons';
import Select from "react-select";
-import { Doc, Opt } from "../../fields/Doc";
+import { Doc } from "../../fields/Doc";
import "./GroupMemberView.scss";
-library.add(fa.faWindowClose);
+library.add(fa.faTimes, fa.faTrashAlt);
interface GroupMemberViewProps {
group: Doc;
@@ -21,17 +21,26 @@ interface GroupMemberViewProps {
@observer
export default class GroupMemberView extends React.Component<GroupMemberViewProps> {
+ @observable private memberSort: "ascending" | "descending" | "none" = "none";
+
private get editingInterface() {
- const members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
+ let members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
+ members = this.memberSort === "ascending" ? members.sort() : this.memberSort === "descending" ? members.sort().reverse() : members;
+
const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
+
return (!this.props.group ? null :
<div className="editing-interface">
<div className="editing-header">
- <b>{this.props.group.groupName}</b>
+ <input
+ className="group-title"
+ value={StrCast(this.props.group.groupName)}
+ onChange={e => this.props.group.groupName = e.currentTarget.value}
+ >
+ </input>
<div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}>
- <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+ <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
</div>
-
{GroupManager.Instance.hasEditAccess(this.props.group) ?
<div className="group-buttons">
<div className="add-member-dropdown">
@@ -42,19 +51,39 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
placeholder={"Add members"}
value={null}
closeMenuOnSelect={true}
+ styles={{
+ dropdownIndicator: (base, state) => ({
+ ...base,
+ transition: '0.5s all ease',
+ transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+ })
+ }}
/>
</div>
<button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button>
</div> :
null}
+ <div
+ className="sort-emails"
+ onClick={action(() => this.memberSort = this.memberSort === "ascending" ? "descending" : this.memberSort === "descending" ? "none" : "ascending")}>
+ Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */}
+ </div>
</div>
+ <hr />
<div className="editing-contents">
{members.map(member => (
- <div className="editing-row">
+ <div
+ className="editing-row"
+ key={member}
+ >
<div className="user-email">
{member}
</div>
- {GroupManager.Instance.hasEditAccess(this.props.group) ? <button onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}> Remove </button> : null}
+ {GroupManager.Instance.hasEditAccess(this.props.group) ?
+ <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
+ <FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} />
+ </div>
+ : null}
</div>
))}
</div>
@@ -68,6 +97,8 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
isDisplayed={true}
interactive={true}
contents={this.editingInterface}
+ dialogueBoxStyle={{ width: 400, height: 250 }}
+ closeOnExternalClick={this.props.onCloseButtonClick}
/>;
}
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 69256ce67..3a7fd7626 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -121,7 +121,11 @@ export namespace InteractionUtils {
}
}
} else {
- pts = points;
+ pts = points.slice();
+ // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here.
+ if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) {
+ pts.pop();
+ }
}
if (isNaN(scalex)) {
scalex = 1;
@@ -171,7 +175,6 @@ export namespace InteractionUtils {
// return (
// InkOptionsMenu.Instance.getColors().map(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} />
// </marker>;
@@ -412,7 +415,6 @@ export namespace InteractionUtils {
// let dist12 = TwoPointEuclidist(pt1, pt2);
// let dist23 = TwoPointEuclidist(pt2, pt3);
// let dist13 = TwoPointEuclidist(pt1, pt3);
- // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
// let dist12close = dist12 < leniency;
// let dist23close = dist23 < leniency;
// let dist13close = dist13 < leniency;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 50f3fc1d6..223f0e7ef 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -2,8 +2,6 @@ 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:
@@ -25,12 +23,12 @@ export class LinkManager {
private static _instance: LinkManager;
-
public static currentLink: Opt<Doc>;
public static get Instance(): LinkManager {
return this._instance || (this._instance = new this());
}
+
private constructor() {
}
@@ -53,7 +51,6 @@ 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);
@@ -63,12 +60,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));
});
@@ -205,7 +207,4 @@ export class LinkManager {
if (Doc.AreProtosEqual(anchor, a2)) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
-}
-
-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
+} \ No newline at end of file
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 785e63d9a..94806a7ba 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -32,24 +32,17 @@ export class ScriptManager {
}
public addScript(scriptDoc: Doc): boolean {
-
- console.log("in add script method");
-
const scriptList = this.getAllScripts();
scriptList.push(scriptDoc);
if (ScriptManager.Instance.ScriptManagerDoc) {
ScriptManager.Instance.ScriptManagerDoc.data = new List<Doc>(scriptList);
ScriptManager.addScriptToGlobals(scriptDoc);
- console.log("script added");
return true;
}
return false;
}
public deleteScript(scriptDoc: Doc): boolean {
-
- console.log("in delete script method");
-
if (scriptDoc.name) {
Scripting.removeGlobal(StrCast(scriptDoc.name));
}
@@ -70,7 +63,6 @@ export class ScriptManager {
Scripting.removeGlobal(StrCast(scriptDoc.name));
const params = Cast(scriptDoc["data-params"], listSpec("string"), []);
- console.log(params);
const paramNames = params.reduce((o: string, p: string) => {
if (params.indexOf(p) === params.length - 1) {
o = o + p.split(":")[0].trim();
@@ -82,8 +74,6 @@ export class ScriptManager {
const f = new Function(paramNames, StrCast(scriptDoc.script));
- console.log(scriptDoc.script);
-
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
let parameters = "(";
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index e6cf50de3..f1e6155d2 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -10,8 +10,6 @@ export { ts };
// @ts-ignore
import * as typescriptlib from '!!raw-loader!./type_decls.d';
import { Doc, Field } from '../../fields/Doc';
-import { Cast } from "../../fields/Types";
-import { listSpec } from "../../fields/Schema";
export interface ScriptSucccess {
success: true;
@@ -95,10 +93,10 @@ export namespace Scripting {
export function removeGlobal(name: string) {
if (getGlobals().includes(name)) {
delete _scriptingGlobals[name];
- if (_scriptingDescriptions[name]){
+ if (_scriptingDescriptions[name]) {
delete _scriptingDescriptions[name];
}
- if (_scriptingParams[name]){
+ if (_scriptingParams[name]) {
delete _scriptingParams[name];
}
return true;
@@ -123,11 +121,11 @@ export namespace Scripting {
return _scriptingGlobals;
}
- export function getDescriptions(){
+ export function getDescriptions() {
return _scriptingDescriptions;
}
- export function getParameters(){
+ export function getParameters() {
return _scriptingParams;
}
}
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 1ac68480e..0a01d8ac7 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -128,7 +128,6 @@ export namespace SearchUtil {
});
const result: IdSearchResult = JSON.parse(response);
const { ids, numFound, highlighting } = result;
- //console.log(ids.length);
const docMap = await DocServer.GetRefFields(ids);
const docs: Doc[] = [];
for (const id of ids) {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 9a968aeda..20d881961 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -22,7 +22,6 @@ export namespace SelectionManager {
}
manager.SelectedDocuments.set(docView, true);
- // console.log(manager.SelectedDocuments);
docView.props.whenActiveChanged(true);
} else if (!ctrlPressed && Array.from(manager.SelectedDocuments.entries()).length > 1) {
Array.from(manager.SelectedDocuments.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false));
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index 6d394a38d..c1627e69f 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -52,6 +52,7 @@
.settings-body {
display: flex;
justify-content: space-between;
+ margin-top: -10;
.settings-type {
display: flex;
@@ -105,6 +106,7 @@
text-transform: uppercase;
letter-spacing: 2px;
font-size: 120%;
+ margin-top: 0;
}
.container {
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f7ca3942b..90d59aa51 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -11,10 +11,12 @@ import { Networking } from "../Network";
import { CurrentUserUtils } from "./CurrentUserUtils";
import { Utils } from "../../Utils";
import { Doc } from "../../fields/Doc";
+import GroupManager from "./GroupManager";
import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import { togglePlaygroundMode } from "../../fields/util";
-library.add(fa.faWindowClose);
+library.add(fa.faTimes);
@observer
export default class SettingsManager extends React.Component<{}> {
@@ -25,6 +27,7 @@ export default class SettingsManager extends React.Component<{}> {
@observable private settingsContent = "password";
@observable private errorText = "";
@observable private successText = "";
+ @observable private playgroundMode = false;
private curr_password_ref = React.createRef<HTMLInputElement>();
private new_password_ref = React.createRef<HTMLInputElement>();
private new_confirm_ref = React.createRef<HTMLInputElement>();
@@ -87,11 +90,17 @@ export default class SettingsManager extends React.Component<{}> {
}
@action
googleAuthorize = (event: any) => {
- GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)
+ GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true);
}
@action
hypothesisAuthorize = (event: any) => {
- HypothesisAuthenticationManager.Instance.fetchAccessToken(true)
+ HypothesisAuthenticationManager.Instance.fetchAccessToken(true);
+ }
+
+ @action
+ togglePlaygroundMode = () => {
+ togglePlaygroundMode();
+ this.playgroundMode = !this.playgroundMode;
}
private get settingsInterface() {
@@ -100,15 +109,17 @@ export default class SettingsManager extends React.Component<{}> {
<div className="settings-heading">
<h1>settings</h1>
<div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+ <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} />
</div>
</div>
<div className="settings-body">
<div className="settings-type">
<button onClick={this.onClick} value="password">reset password</button>
<button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
+ <button onClick={this.togglePlaygroundMode}>{`${this.playgroundMode ? "Disable" : "Enable"} playground mode`}</button>
<button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
<button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
+ <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
<button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
@@ -144,8 +155,7 @@ export default class SettingsManager extends React.Component<{}> {
contents={this.settingsInterface}
isDisplayed={this.isOpen}
interactive={true}
- dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
- overlayDisplayedOpacity={this.overlayOpacity}
+ closeOnExternalClick={this.close}
/>
);
}
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index fcbc05f8a..130785672 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -1,29 +1,112 @@
-@import "../views/globalCssVariables";
-
.sharing-interface {
- display: flex;
- flex-direction: column;
- width: 730px;
-
- .dialogue-box {
- width: 450;
- height: 300;
- }
+ width: 600px;
+ height: 360px;
.overlay {
transform: translate(-20px, -20px);
}
+ select {
+ text-align: justify;
+ text-align-last: end
+ }
+
.sharing-contents {
display: flex;
+ flex-direction: column;
+
+ .close-button {
+ position: absolute;
+ right: 1em;
+ top: 1em;
+ cursor: pointer;
+ z-index: 999;
+ }
+
+ .share-setup {
+ display: flex;
+ margin-bottom: 20px;
+ align-items: center;
+ height: 36;
+
+ .user-search {
+ width: 90%;
+
+ input {
+ height: 30;
+ }
+ }
+
+ .permissions-select {
+ z-index: 1;
+ margin-left: -100;
+ border: none;
+ outline: none;
+ text-align: justify; // for Edge
+ text-align-last: end;
+ }
+
+ .share-button {
+ height: 105%;
+ margin-left: 2%;
+ background-color: #979797;
+ }
+ }
+
+ .main-container {
+ display: flex;
+ margin-top: -10px;
+
+ .individual-container,
+ .group-container {
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+
+ .user-sort {
+ text-align: left;
+ margin-left: 10;
+ cursor: pointer;
+ }
+
+ .share-title {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
+
+ .groups-list,
+ .users-list {
+ font-style: italic;
+ background: #e8e8e8;
+ padding-left: 10px;
+ padding-right: 10px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ text-align: left;
+ display: flex;
+ align-content: center;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+ color: black;
+ height: 250px;
+ margin: 0 2;
+
+
+ .none {
+ font-style: italic;
+
+ }
+ }
+ }
+ }
button {
- background: $darker-alt-accent;
outline: none;
border-radius: 5px;
border: 0px;
color: #fcfbf7;
- text-transform: uppercase;
+ text-transform: none;
letter-spacing: 2px;
font-size: 75%;
padding: 0 10;
@@ -31,37 +114,6 @@
transition: transform 0.2s;
height: 25;
}
-
- .individual-container,
- .group-container {
- width: 50%;
-
- .share-groups,
- .share-individual {
- margin-top: 20px;
- margin-bottom: 20px;
- }
-
- .groups-list,
- .users-list {
- font-style: italic;
- background: white;
- border: 1px solid black;
- padding-left: 10px;
- padding-right: 10px;
- overflow-y: scroll;
- overflow-x: hidden;
- text-align: left;
- display: flex;
- align-content: center;
- align-items: center;
- text-align: center;
- justify-content: center;
- color: red;
- height: 150px;
- margin: 0 2;
- }
- }
}
.focus-span {
@@ -69,11 +121,10 @@
}
p {
- font-size: 15px;
+ font-size: 20px;
text-align: left;
- font-style: italic;
- padding: 0;
margin: 0 0 20px 0;
+ color: black;
}
.hr-substitute {
@@ -108,30 +159,41 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- width: 700px;
- min-width: 700px;
- max-width: 700px;
+ width: 100%;
text-align: left;
font-style: normal;
font-size: 14;
font-weight: normal;
padding: 0;
- align-items: baseline;
+ align-items: center;
+
+ .group-info {
+ cursor: pointer;
+ }
+
+ &:hover .padding {
+ white-space: unset;
+ }
.padding {
padding: 0 10px 0 0;
color: black;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 40%;
}
.permissions-dropdown {
- outline: none;
+ border: none;
height: 25;
+ background-color: #e8e8e8;
}
.edit-actions {
display: flex;
position: absolute;
- right: 51.5%;
+ right: -10;
}
}
@@ -171,18 +233,4 @@
padding-top: 12px;
}
}
-
- .close-button {
- border-radius: 5px;
- margin-top: 20px;
- padding: 10px 0;
- background: aliceblue;
- transition: 0.5s ease all;
- border: 1px solid;
- border-color: aliceblue;
- }
-
- .close-button:hover {
- border-color: black;
- }
} \ No newline at end of file
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 127ee33ce..452a58d21 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,15 +1,13 @@
import { observable, runInAction, action } from "mobx";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocCastAsync } from "../../fields/Doc";
+import { Doc, Opt, DocListCastAsync } from "../../fields/Doc";
import { DocServer } from "../DocServer";
import { Cast, StrCast } from "../../fields/Types";
import * as RequestPromise from "request-promise";
import { Utils } from "../../Utils";
import "./SharingManager.scss";
-import { Id } from "../../fields/FieldSymbols";
import { observer } from "mobx-react";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { DocumentView } from "../views/nodes/DocumentView";
@@ -17,46 +15,32 @@ import { SelectionManager } from "./SelectionManager";
import { DocumentManager } from "./DocumentManager";
import { CollectionView } from "../views/collections/CollectionView";
import { DictationOverlay } from "../views/DictationOverlay";
-import GroupManager from "./GroupManager";
+import GroupManager, { UserOptions } from "./GroupManager";
import GroupMemberView from "./GroupMemberView";
+import Select from "react-select";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { List } from "../../fields/List";
+import { distributeAcls, SharingPermissions } from "../../fields/util";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-library.add(fa.faCopy);
+library.add(fa.faCopy, fa.faTimes);
export interface User {
email: string;
userDocumentId: string;
}
-export enum SharingPermissions {
- None = "Not Shared",
- View = "Can View",
- Add = "Can Add",
- Edit = "Can Edit"
+interface GroupOptions {
+ label: string;
+ options: UserOptions[];
}
-const ColorMapping = new Map<string, string>([
- [SharingPermissions.None, "red"],
- [SharingPermissions.View, "maroon"],
- [SharingPermissions.Add, "blue"],
- [SharingPermissions.Edit, "green"]
-]);
-
-const HierarchyMapping = new Map<string, string>([
- [SharingPermissions.None, "0"],
- [SharingPermissions.View, "1"],
- [SharingPermissions.Add, "2"],
- [SharingPermissions.Edit, "3"],
-
- ["0", SharingPermissions.None],
- ["1", SharingPermissions.View],
- ["2", SharingPermissions.Add],
- ["3", SharingPermissions.Edit]
-
-]);
+// const SharingKey = "sharingPermissions";
+// const PublicKey = "publicLinkPermissions";
+// const DefaultColor = "black";
-const SharingKey = "sharingPermissions";
-const PublicKey = "publicLinkPermissions";
-const DefaultColor = "black";
+const groupType = "!groupType/";
+const indType = "!indType/";
interface ValidatedUser {
user: User;
@@ -70,17 +54,22 @@ export default class SharingManager extends React.Component<{}> {
public static Instance: SharingManager;
@observable private isOpen = false;
@observable private users: ValidatedUser[] = [];
- @observable private groups: Doc[] = [];
@observable private targetDoc: Doc | undefined;
@observable private targetDocView: DocumentView | undefined;
- @observable private copied = false;
+ // @observable private copied = false;
@observable private dialogueBoxOpacity = 1;
@observable private overlayOpacity = 0.4;
- @observable private groupToView: Opt<Doc>;
+ @observable private selectedUsers: UserOptions[] | null = null;
+ @observable private permissions: SharingPermissions = SharingPermissions.Edit;
+ @observable private individualSort: "ascending" | "descending" | "none" = "none";
+ @observable private groupSort: "ascending" | "descending" | "none" = "none";
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
- private get linkVisible() {
- return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
- }
+
+
+ // private get linkVisible() {
+ // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
+ // }
public open = (target: DocumentView) => {
SelectionManager.DeselectAll();
@@ -89,32 +78,23 @@ export default class SharingManager extends React.Component<{}> {
this.targetDoc = target.props.Document;
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = true;
- if (!this.sharingDoc) {
- this.sharingDoc = new Doc;
- }
+ this.permissions = SharingPermissions.Edit;
}));
- runInAction(() => this.groups = GroupManager.Instance.getAllGroupsCopy());
}
public close = action(() => {
this.isOpen = false;
this.users = [];
+ this.selectedUsers = null;
+
setTimeout(action(() => {
- this.copied = false;
+ // this.copied = false;
DictationOverlay.Instance.hasActiveModal = false;
this.targetDoc = undefined;
}), 500);
});
- private get sharingDoc() {
- return this.targetDoc ? Cast(this.targetDoc[SharingKey], Doc) as Doc : undefined;
- }
-
- private set sharingDoc(value: Doc | undefined) {
- this.targetDoc && (this.targetDoc[SharingKey] = value);
- }
-
constructor(props: {}) {
super(props);
SharingManager.Instance = this;
@@ -142,98 +122,118 @@ export default class SharingManager extends React.Component<{}> {
setInternalGroupSharing = (group: Doc, permission: string) => {
const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
- const sharingDoc = this.sharingDoc!;
- if (permission === SharingPermissions.None) {
- const metadata = sharingDoc[StrCast(group.groupName)];
- if (metadata) sharingDoc[StrCast(group.groupName)] = undefined;
- }
- else {
- sharingDoc[StrCast(group.groupName)] = permission;
- }
+ const target = this.targetDoc!;
+ const ACL = `ACL-${StrCast(group.groupName)}`;
+ // fix this - not needed (here and setinternalsharing and removegroup)
+ // target[ACL] = permission;
+ // Doc.GetProto(target)[ACL] = permission;
+
+ distributeAcls(ACL, permission as SharingPermissions, this.targetDoc!);
+
+ group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
- users.forEach(user => {
- this.setInternalSharing(user, permission, group);
+ users.forEach(({ notificationDoc }) => {
+ DocListCastAsync(notificationDoc[storage]).then(resolved => {
+ if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+ else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+ });
});
}
- setInternalSharing = async (recipient: ValidatedUser, state: string, group: Opt<Doc>) => {
- const { user, notificationDoc } = recipient;
- const target = this.targetDoc!;
- const manager = this.sharingDoc!;
- const key = user.userDocumentId;
-
- let metadata = await DocCastAsync(manager[key]);
- const permissions: { [key: string]: number } = metadata?.permissions ? JSON.parse(StrCast(metadata.permissions)) : {};
- permissions[StrCast(group ? group.groupName : Doc.CurrentUserEmail)] = parseInt(HierarchyMapping.get(state)!);
- const max = Math.max(...Object.values(permissions));
-
- // let max = 0;
- // const keys: string[] = [];
- // for (const [key, value] of Object.entries(permissions)) {
- // if (value === max && max !== 0) {
- // keys.push(key);
- // }
- // else if (value > max) {
- // keys.splice(0, keys.length);
- // keys.push(key);
- // max = value;
- // }
- // }
-
- switch (max) {
- case 0:
- if (metadata) {
- const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
- Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
- manager[key] = undefined;
- }
- break;
-
- case 1: case 2: case 3:
- if (!metadata) {
- metadata = new Doc;
- const sharedAlias = Doc.MakeAlias(target);
- Doc.AddDocToList(notificationDoc, storage, sharedAlias);
- metadata.sharedAlias = sharedAlias;
- manager[key] = metadata;
- }
- metadata.permissions = JSON.stringify(permissions);
- // metadata.usersShared = JSON.stringify(keys);
- break;
- }
+ shareWithAddedMember = (group: Doc, emailId: string) => {
+ const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
- if (metadata) metadata.maxPermission = HierarchyMapping.get(`${max}`);
+ if (group.docsShared) {
+ DocListCastAsync(group.docsShared).then(docsShared => {
+ docsShared?.forEach(doc => {
+ DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
+ });
+ });
+ }
}
- private setExternalSharing = (state: string) => {
- const sharingDoc = this.sharingDoc;
- if (!sharingDoc) {
- return;
+ removeMember = (group: Doc, emailId: string) => {
+ const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
+
+ if (group.docsShared) {
+ DocListCastAsync(group.docsShared).then(docsShared => {
+ docsShared?.forEach(doc => {
+ DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+ });
+ });
}
- sharingDoc[PublicKey] = state;
}
- private get sharingUrl() {
- if (!this.targetDoc) {
- return undefined;
+ removeGroup = (group: Doc) => {
+ if (group.docsShared) {
+ DocListCastAsync(group.docsShared).then(resolved => {
+ resolved?.forEach(doc => {
+ const ACL = `ACL-${StrCast(group.groupName)}`;
+ // doc[ACL] = doc[DataSym][ACL] = "Not Shared";
+
+ distributeAcls(ACL, SharingPermissions.None, doc);
+
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+
+ users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc));
+ });
+
+ });
}
- const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
- return `${baseUrl}?sharing=true`;
}
- copy = action(() => {
- if (this.sharingUrl) {
- Utils.CopyText(this.sharingUrl);
- this.copied = true;
+ // @action
+ setInternalSharing = (recipient: ValidatedUser, permission: string) => {
+ const { user, notificationDoc } = recipient;
+ const target = this.targetDoc!;
+ const key = user.email.replace('.', '_');
+ const ACL = `ACL-${key}`;
+
+ distributeAcls(ACL, permission as SharingPermissions, this.targetDoc!);
+
+ if (permission !== SharingPermissions.None) {
+ DocListCastAsync(notificationDoc[storage]).then(resolved => {
+ Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+ });
}
- });
+ else {
+ DocListCastAsync(notificationDoc[storage]).then(resolved => {
+ Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+ });
+ }
+ }
+
+
+ // private setExternalSharing = (permission: string) => {
+ // const sharingDoc = this.sharingDoc;
+ // if (!sharingDoc) {
+ // return;
+ // }
+ // sharingDoc[PublicKey] = permission;
+ // }
+
+ // private get sharingUrl() {
+ // if (!this.targetDoc) {
+ // return undefined;
+ // }
+ // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
+ // return `${baseUrl}?sharing=true`;
+ // }
+
+ // copy = action(() => {
+ // if (this.sharingUrl) {
+ // Utils.CopyText(this.sharingUrl);
+ // this.copied = true;
+ // }
+ // });
private get sharingOptions() {
return Object.values(SharingPermissions).map(permission => {
return (
- <option key={permission} value={permission}>
+ <option key={permission} value={permission} selected={permission === SharingPermissions.Edit}>
{permission}
</option>
);
@@ -270,33 +270,151 @@ export default class SharingManager extends React.Component<{}> {
);
}
- private computePermissions = (userKey: string) => {
- const sharingDoc = this.sharingDoc;
- if (!sharingDoc) {
- return SharingPermissions.None;
- }
- const metadata = sharingDoc[userKey] as Doc | string;
- if (!metadata) {
- return SharingPermissions.None;
+ @action
+ handleUsersChange = (selectedOptions: any) => {
+ this.selectedUsers = selectedOptions as UserOptions[];
+ }
+
+ @action
+ handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
+ this.permissions = event.currentTarget.value as SharingPermissions;
+ }
+
+ @action
+ share = () => {
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
+ }
+ else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
+ TaskCompletionBox.popupX = left - 1.5 * width;
+ TaskCompletionBox.popupY = top - height;
+ TaskCompletionBox.textDisplayed = "Document shared!";
+ TaskCompletionBox.taskCompleted = true;
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
+ this.selectedUsers = null;
}
- return StrCast(metadata instanceof Doc ? metadata.maxPermission : metadata, SharingPermissions.None);
+ }
+
+ sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
+ const { email: e1 } = u1.user;
+ const { email: e2 } = u2.user;
+ return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
+ }
+
+ sortGroups = (group1: Doc, group2: Doc) => {
+ const g1 = StrCast(group1.groupName);
+ const g2 = StrCast(group2.groupName);
+ return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
}
private get sharingInterface() {
- const existOtherUsers = this.users.length > 0;
- const existGroups = this.groups.length > 0;
+ const groupList = GroupManager.Instance?.getAllGroups() || [];
+
+ const sortedUsers = this.users.sort(this.sortUsers)
+ .map(({ user: { email } }) => ({ label: email, value: indType + email }));
+ const sortedGroups = groupList.sort(this.sortGroups)
+ .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
+
+ const options: GroupOptions[] = GroupManager.Instance ?
+ [
+ {
+ label: 'Individuals',
+ options: sortedUsers
+ },
+ {
+ label: 'Groups',
+ options: sortedGroups
+ }
+ ]
+ : [];
+
+ const users = this.individualSort === "ascending" ? this.users.sort(this.sortUsers) : this.individualSort === "descending" ? this.users.sort(this.sortUsers).reverse() : this.users;
+ const groups = this.groupSort === "ascending" ? groupList.sort(this.sortGroups) : this.groupSort === "descending" ? groupList.sort(this.sortGroups).reverse() : groupList;
+
+ const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => {
+ const userKey = user.email.replace('.', '_');
+ const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None);
+
+ return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : (
+ <div
+ key={userKey}
+ className={"container"}
+ >
+ <span className={"padding"}>{user.email}</span>
+ <div className="edit-actions">
+ <select
+ className={"permissions-dropdown"}
+ value={permissions}
+ onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
+ >
+ {this.sharingOptions}
+ </select>
+ </div>
+ </div>
+ );
+ });
- // const manager = this.sharingDoc!;
+ userListContents.unshift(
+ (
+ <div
+ key={"owner"}
+ className={"container"}
+ >
+ <span className={"padding"}>{this.targetDoc?.author}</span>
+ <div className="edit-actions">
+ <div className={"permissions-dropdown"}>
+ Owner
+ </div>
+ </div>
+ </div>
+ )
+ );
+
+ const groupListContents = groups.map(group => {
+ const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None);
+
+ return permissions === SharingPermissions.None ? null : (
+ <div
+ key={StrCast(group.groupName)}
+ className={"container"}
+ >
+ <div className={"padding"}>{group.groupName}</div>
+ <div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
+ <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+ </div>
+ <div className="edit-actions">
+ <select
+ className={"permissions-dropdown"}
+ value={permissions}
+ onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
+ >
+ {this.sharingOptions}
+ </select>
+ </div>
+ </div>
+ );
+ });
+
+ const displayUserList = !userListContents?.every(user => user === null);
+ const displayGroupList = !groupListContents?.every(group => group === null);
return (
<div className={"sharing-interface"}>
- {this.groupToView ?
+ {GroupManager.Instance?.currentGroup ?
<GroupMemberView
- group={this.groupToView}
- onCloseButtonClick={action(() => this.groupToView = undefined)}
+ group={GroupManager.Instance.currentGroup}
+ onCloseButtonClick={action(() => GroupManager.Instance.currentGroup = undefined)}
/> :
null}
- <p className={"share-link"}>Manage the public link to {this.focusOn("this document...")}</p>
+ {/* <p className={"share-link"}>Manage the public link to {this.focusOn("this document...")}</p>
{!this.linkVisible ? (null) :
<div className={"link-container"}>
<div className={"link-box"} onClick={this.copy}>{this.sharingUrl}</div>
@@ -325,86 +443,81 @@ export default class SharingManager extends React.Component<{}> {
{this.sharingOptions}
</select>
</div>
- <div className={"hr-substitute"} />
+ <div className={"hr-substitute"} /> */}
<div className="sharing-contents">
- <div className={"individual-container"}>
- <p className={"share-individual"}>Privately share {this.focusOn("this document")} with an individual...</p>
- <div className={"users-list"} style={{ display: existOtherUsers ? "block" : "flex", minHeight: existOtherUsers ? undefined : 150 }}>{/*200*/}
- {!existOtherUsers ? "There are no other users in your database." :
- this.users.map(({ user, notificationDoc }) => { // can't use async here
- const userKey = user.userDocumentId;
- const permissions = this.computePermissions(userKey);
- const color = ColorMapping.get(permissions);
-
- // console.log(manager);
- // const metadata = manager[userKey] as Doc;
- // const usersShared = StrCast(metadata?.usersShared, "");
- // console.log(usersShared)
-
-
- return (
+ <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
+ <div className={"close-button"} onClick={this.close}>
+ <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+ </div>
+ {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
+ :
+ <div className="share-setup">
+ <Select
+ className={"user-search"}
+ placeholder={"Enter user or group name..."}
+ isMulti
+ closeMenuOnSelect={false}
+ options={options}
+ onChange={this.handleUsersChange}
+ value={this.selectedUsers}
+ />
+ <select className="permissions-select" onChange={this.handlePermissionsChange}>
+ {this.sharingOptions}
+ </select>
+ <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
+ Share
+ </button>
+ </div>
+ }
+ <div className="main-container">
+ <div className={"individual-container"}>
+ <div
+ className="user-sort"
+ onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
+ Individuals {this.individualSort === "ascending" ? "↑" : this.individualSort === "descending" ? "↓" : ""} {/* → */}
+ </div>
+ <div className={"users-list"} style={{ display: !displayUserList ? "flex" : "block" }}>{/*200*/}
+ {
+ !displayUserList ?
<div
- key={userKey}
- className={"container"}
+ className={"none"}
>
- <span className={"padding"}>{user.email}</span>
- {/* <div className={"shared-by"}>{usersShared}</div> */}
- <div className="edit-actions">
- <select
- className={"permissions-dropdown"}
- value={permissions}
- style={{ color, borderColor: color }}
- onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value, undefined)}
- >
- {this.sharingOptions}
- </select>
- </div>
+ There are no users this document has been shared with.
</div>
- );
- })
- }
+ :
+ userListContents
+ }
+ </div>
</div>
- </div>
- <div className={"group-container"}>
- <p className={"share-groups"}>Privately share {this.focusOn("this document")} with a group...</p>
- <div className={"groups-list"} style={{ display: existGroups ? "block" : "flex", minHeight: existOtherUsers ? undefined : 150 }}>{/*200*/}
- {!existGroups ? "There are no groups in your database." :
- this.groups.map(group => {
- const permissions = this.computePermissions(StrCast(group.groupName));
- const color = ColorMapping.get(permissions);
- return (
+ <div className={"group-container"}>
+ <div
+ className="user-sort"
+ onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
+ Groups {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
+
+ </div>
+ <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/}
+ {
+ !displayGroupList ?
<div
- key={StrCast(group.groupName)}
- className={"container"}
+ className={"none"}
>
- <span className={"padding"}>{group.groupName}</span>
- <div className="edit-actions">
- <select
- className={"permissions-dropdown"}
- value={permissions}
- style={{ color, borderColor: color }}
- onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
- >
- {this.sharingOptions}
- </select>
- <button onClick={action(() => this.groupToView = group)}>Edit</button>
+ There are no groups this document has been shared with.
</div>
- </div>
- );
- })
-
- }
+ :
+ groupListContents
+ }
+ </div>
</div>
</div>
+
</div>
- <div className={"close-button"} onClick={this.close}>Done</div>
</div>
);
}
render() {
- // console.log(this.sharingDoc);
return (
<MainViewModal
contents={this.sharingInterface}
@@ -412,6 +525,7 @@ export default class SharingManager extends React.Component<{}> {
interactive={true}
dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
overlayDisplayedOpacity={this.overlayOpacity}
+ closeOnExternalClick={this.close}
/>
);
}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 1bf242d93..86e0a568a 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -42,7 +42,7 @@
.contextMenu-item {
// width: 11vw; //10vw
- height: 30px; //2vh
+ height: 25px; //2vh
background: whitesmoke;
display: flex; //comment out to allow search icon to be inline with search text
justify-content: left;
@@ -70,9 +70,6 @@
text-align: center;
font-size: 20px;
margin-left: 5px;
- margin-top: 5px;
- margin-bottom: 5px;
- height: 20px;
}
}
.contextMenu-description {
@@ -90,14 +87,11 @@
border-style: none;
// padding: 10px 0px 10px 0px;
white-space: nowrap;
- font-size: 13px;
+ font-size: 10px;
color: grey;
- letter-spacing: 2px;
+ letter-spacing: 1px;
text-transform: uppercase;
padding-right: 30px;
- margin-top: 5px;
- height: 20px;
- margin-bottom: 5px;
}
.contextMenu-item:hover {
@@ -138,6 +132,10 @@
padding-left: 5px;
}
+.contextMenu-inlineMenu {
+ border-top: solid 1px;
+}
+
.contextMenu-item:hover {
transition: all 0.1s ease;
background: $lighter-alt-accent;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 68ebd8e14..81432968d 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -20,6 +20,7 @@ export interface SubmenuProps {
description: string;
subitems: ContextMenuProps[];
noexpand?: boolean;
+ addDivider?: boolean;
icon: IconProp; //maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -98,12 +99,13 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
if (!("noexpand" in this.props)) {
- return <>
+ return <div className="contextMenu-inlineMenu">
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
- </>;
+ </div>;
}
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")}
+ style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
<span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center" }}>
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 65770c0bb..9ed14509f 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -66,6 +66,7 @@ export class DictationOverlay extends React.Component {
interactive={false}
dialogueBoxStyle={dialogueBoxStyle}
overlayStyle={overlayStyle}
+ closeOnExternalClick={this.initiateDictationFade}
/>);
}
} \ No newline at end of file
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9b9a28f0f..9fd406407 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast, AclSym, AclReadonly, AclAddonly } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -7,6 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { List } from '../../fields/List';
import { DateField } from '../../fields/DateField';
import { ScriptField } from '../../fields/ScriptField';
+import { GetEffectiveAcl, getPlaygroundMode, SharingPermissions } from '../../fields/util';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -91,6 +92,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
// key where data is stored
@computed get fieldKey() { return this.props.fieldKey; }
+ private AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit]
+ ]);
+
lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
styleFromLayoutString = (scale: number) => {
@@ -114,14 +122,16 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
@action.bound
removeDocument(doc: Doc | Doc[]): boolean {
const docs = doc instanceof Doc ? [doc] : doc;
- docs.map(doc => doc.annotationOn = undefined);
+ docs.map(doc => doc.isPushpin = doc.annotationOn = undefined);
const targetDataDoc = this.dataDoc;
const value = DocListCast(targetDataDoc[this.annotationKey]);
- const result = value.filter(v => !docs.includes(v));
- if (result.length !== value.length) {
- targetDataDoc[this.annotationKey] = new List<Doc>(result);
+ const toRemove = value.filter(v => docs.includes(v));
+ // can't assign new List<Doc>(result) to this because you can't assign new values in addonly
+ if (toRemove.length !== 0) {
+ toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.annotationKey, doc));
return true;
}
+
return false;
}
// if the moved document is already in this overlay collection nothing needs to be done.
@@ -137,15 +147,30 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.annotationKey]);
const added = docs.filter(d => !docList.includes(d));
+ const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+
if (added.length) {
- if (this.dataDoc[AclSym] === AclReadonly) {
+ if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
return false;
- } else if (this.dataDoc[AclSym] === AclAddonly) {
- added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
- } else {
- added.map(doc => doc.context = this.props.Document);
- targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ else {
+ if (this.props.Document[AclSym]) {
+ added.forEach(d => {
+ const dataDoc = d[DataSym];
+ dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+ for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+ dataDoc[key] = d[key] = this.AclMap.get(value);
+ }
+ });
+ }
+ if (effectiveAcl === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
+ }
+ else {
+ added.map(doc => doc.context = this.props.Document);
+ targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
}
return true;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c188618f4..c99034d81 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -51,7 +51,6 @@ enum UtilityButtonState {
@observer
export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> {
- private _linkButton = React.createRef<HTMLDivElement>();
private _dragRef = React.createRef<HTMLDivElement>();
private _pullAnimating = false;
private _pushAnimating = false;
@@ -118,7 +117,7 @@ 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) : <Tooltip title={`${published ? "Push" : "Publish"} to Google Docs`}>
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
<div
className="documentButtonBar-linker"
style={{ animation }}
@@ -137,46 +136,49 @@ 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";
+
+ 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={(() => {
- 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";
- }
- })()}
- ><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;
- }
- })}
- 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;
+ 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;
+ }
+ })}
+ 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);
}
- 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={(() => {
@@ -194,7 +196,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get pinButton() {
const targetDoc = this.view0?.props.Document;
const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
- return !targetDoc ? (null) : <Tooltip title={Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}>
+ 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)}>
@@ -206,14 +208,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get metadataButton() {
const view0 = this.view0;
- return !view0 ? (null) : <Tooltip title="Show metadata panel"><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>;
+ 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
@@ -254,7 +257,7 @@ 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) :
- <Tooltip title="Tap: Customize layout. Drag: Create alias" >
+ <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} />}>
@@ -275,9 +278,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div className="documentButtonBar-button">
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
</div>
- <div className="documentButtonBar-button">
+ {DocumentLinksButton.StartLink ? <div className="documentButtonBar-button">
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
- </div>
+ </div> : null}
<div className="documentButtonBar-button">
{this.templateButton}
</div>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 5948ada88..424a06431 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -157,6 +157,7 @@ $linkGap : 3px;
grid-column-end: 2;
pointer-events: all;
padding-left: 5px;
+ cursor: pointer;
}
.documentDecorations-title {
@@ -204,7 +205,7 @@ $linkGap : 3px;
width: 20px;
}
-.documentDecorations-closeButton {
+.documentDecorations-openInTab {
opacity: 1;
grid-column-start: 4;
grid-column-end: 5;
@@ -216,7 +217,7 @@ $linkGap : 3px;
margin-top: auto;
}
-.documentDecorations-minimizeButton {
+.documentDecorations-closeButton {
opacity: 1;
grid-column-start: 1;
grid-column-end: 3;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index baf9865fe..3d258430e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -84,6 +84,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;
}
@@ -91,7 +92,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;
@@ -153,7 +154,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (e.button === 0 && !e.altKey && !e.ctrlKey) {
let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
while (child.children.length) {
- const next = Array.from(child.children).find(c => typeof (c.className) !== "string" || !c.className.includes("collectionViewChrome"));
+ 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;
@@ -295,13 +296,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const doc = Document(element.rootDoc);
if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
doc.rotation = Number(doc.rotation) + Number(angle);
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
-
+ const inks = Cast(doc.data, InkField)?.inkData;
+ if (inks) {
const newPoints: { X: number, Y: number }[] = [];
- for (var i = 0; i < ink.length; i++) {
- const newX = Math.cos(angle) * (ink[i].X - this._centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - this._centerPoints[index].Y) + this._centerPoints[index].X;
- const newY = Math.sin(angle) * (ink[i].X - this._centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - this._centerPoints[index].Y) + this._centerPoints[index].Y;
+ for (const ink of inks) {
+ const newX = Math.cos(angle) * (ink.X - this._centerPoints[index].X) - Math.sin(angle) * (ink.Y - this._centerPoints[index].Y) + this._centerPoints[index].X;
+ const newY = Math.sin(angle) * (ink.X - this._centerPoints[index].X) + Math.cos(angle) * (ink.Y - this._centerPoints[index].Y) + this._centerPoints[index].Y;
newPoints.push({ X: newX, Y: newY });
}
doc.data = new InkField(newPoints);
@@ -389,7 +389,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
move[1] = thisPt.thisY - this._snapY;
this._snapX = thisPt.thisX;
this._snapY = thisPt.thisY;
-
+ let dragBottom = false;
let dX = 0, dY = 0, dW = 0, dH = 0;
const unfreeze = () =>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
@@ -427,6 +427,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
case "documentDecorations-bottomResizer":
unfreeze();
dH = move[1];
+ dragBottom = true;
break;
case "documentDecorations-leftResizer":
unfreeze();
@@ -447,13 +448,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let nheight = doc._nativeHeight || 0;
const width = (doc._width || 0);
let height = (doc._height || (nheight / nwidth * width));
+ height = !height || isNaN(height) ? 20 : height;
const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
if (nwidth && nheight) {
if (nwidth / nheight !== width / height) {
height = nheight / nwidth * width;
}
- if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
- else dW = dH * nwidth / nheight;
+ if (!e.ctrlKey && (!dragBottom || !element.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+ if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
+ else dW = dH * nwidth / nheight;
+ }
}
const actualdW = Math.max(width + (dW * scale), 20);
const actualdH = Math.max(height + (dH * scale), 20);
@@ -477,20 +481,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
else if (nwidth > 0 && nheight > 0) {
if (Math.abs(dW) > Math.abs(dH)) {
- if (!fixedAspect) {
+ if (!fixedAspect || e.ctrlKey) {
doc._nativeWidth = actualdW / (doc._width || 1) * (doc._nativeWidth || 0);
}
doc._width = actualdW;
if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width;
- else doc._height = actualdH;
+ else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH;
}
else {
- if (!fixedAspect) {
+ if (!fixedAspect || e.ctrlKey || (dragBottom && element.layoutDoc._fitWidth)) {
doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0);
}
doc._height = actualdH;
if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height;
- else doc._width = actualdW;
+ else if (!fixedAspect || !e.ctrlKey) doc._width = actualdW;
}
} else {
dW && (doc._width = actualdW);
@@ -581,12 +585,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
const minimal = bounds.r - bounds.x < 100 ? true : false;
const maximizeIcon = minimal ? (
- <Tooltip title="Show context menu" placement="top">
+ <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="Iconify" placement="top">
- <div className="documentDecorations-minimizeButton" onClick={this.onCloseClick}>
+ <Tooltip title={<><div className="dash-tooltip">Delete</div></>} placement="top">
+ <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
<FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
</div></Tooltip>);
@@ -609,7 +613,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>}
</> :
<>
- {minimal ? (null) : <Tooltip title="Show context menu" placement="top"><div className="documentDecorations-contextMenu" key="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></Tooltip>}
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
@@ -653,11 +657,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
{maximizeIcon}
{titleArea}
{SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
- <Tooltip title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} placement="top">
+ <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="Open Document in Tab" placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
+ <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}>
{SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
</div></Tooltip>
<div id="documentDecorations-rotation" className="documentDecorations-rotation"
@@ -680,7 +684,7 @@ 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) :
- <Tooltip title="tap to select containing document" placement="top">
+ <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" />
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 25a87ab56..ad61d3f91 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -73,7 +73,6 @@ export class EditableView extends React.Component<EditableProps> {
// // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
// // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
// // to false. this will no longer do so -syip
- // console.log("props editing = " + nextProps.editing);
// if (nextProps.editing && nextProps.editing !== this._editing) {
// this._editing = nextProps.editing;
// EditableView.loadId = "";
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 9faf5e6a5..53bc01b3d 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -24,6 +24,7 @@ import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
import * as fitCurve from 'fit-curve';
+import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
@observer
export default class GestureOverlay extends Touchable {
@@ -161,7 +162,6 @@ export default class GestureOverlay extends Touchable {
if (nts.nt.length === 1) {
// -- radial menu code --
this._holdTimer = setTimeout(() => {
- console.log("hold");
const target = document.elementFromPoint(te.changedTouches?.item(0).clientX, te.changedTouches?.item(0).clientY);
const pt: any = te.touches[te.touches?.length - 1];
if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) {
@@ -604,13 +604,12 @@ export default class GestureOverlay extends Touchable {
break;
}
}
- //if any of the shape is activated in the InkOptionsMenu
+ //if any of the shape is activated in the CollectionFreeFormViewChrome
else if (this.InkShape) {
this.makePolygon(this.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
- if (InkOptionsMenu.Instance._double === "") {
-
+ if (!CollectionFreeFormViewChrome.Instance._keepMode) {
this.InkShape = "";
}
}
@@ -657,9 +656,9 @@ export default class GestureOverlay extends Touchable {
this._points = [];
}
//get out of ink mode after each stroke=
- if (InkOptionsMenu.Instance._double === "") {
+ if (!CollectionFreeFormViewChrome.Instance._keepMode) {
Doc.SetSelectedTool(InkTool.None);
- InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
+ CollectionFreeFormViewChrome.Instance._selected = CollectionFreeFormViewChrome.Instance._shapesNum;
SetActiveArrowStart("none");
GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
SetActiveArrowEnd("none");
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index a3a023164..086085db5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -22,6 +22,8 @@ import { DocumentView } from "./nodes/DocumentView";
import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
import PDFMenu from "./pdf/PDFMenu";
import { ContextMenu } from "./ContextMenu";
+import GroupManager from "../util/GroupManager";
+import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -107,6 +109,8 @@ export default class KeyManager {
GoogleAuthenticationManager.Instance.cancel();
HypothesisAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
+ GroupManager.Instance.close();
+ CollectionFreeFormViewChrome.Instance.clearKeep();
break;
case "delete":
case "backspace":
@@ -305,13 +309,13 @@ export default class KeyManager {
const targetDataDoc = Doc.GetProto(first.props.Document);
const fieldKey = Doc.LayoutFieldKey(first.props.Document);
const docList = DocListCast(targetDataDoc[fieldKey]);
- docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => {
+ docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
count++;
if (doc instanceof Doc) {
list.push(doc);
}
if (count === docids.length) {
- const added = list.filter(d => !docList.includes(d)).map(d => clone ? Doc.MakeClone(d) : d);
+ const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? (await Doc.MakeClone(d)).clone : d));
if (added.length) {
added.map(doc => doc.context = targetDataDoc);
undoBatch(() => {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b03d8e79b..5892e8346 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -78,6 +78,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this._controlNum = 0;
}
+ public static MaskDim = 50000;
render() {
TraceMobx();
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
@@ -168,16 +169,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
height={height}
style={{
pointerEvents: this.props.Document.isInkMask ? "all" : "none",
- transform: this.props.Document.isInkMask ? "translate(2500px, 2500px)" : undefined,
+ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
overflow: "visible",
}}
onContextMenu={() => {
const cm = ContextMenu.Instance;
if (cm) {
- cm.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
+ !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
- cm.addItem({ description: "Format Shape", event: this.formatShape, icon: "paint-brush" });
+ cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
}
}}
><defs>
diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx
index 1dc156968..13d52db88 100644
--- a/src/client/views/KeyphraseQueryView.tsx
+++ b/src/client/views/KeyphraseQueryView.tsx
@@ -11,7 +11,6 @@ export interface KP_Props {
export class KeyphraseQueryView extends React.Component<KP_Props>{
constructor(props: KP_Props) {
super(props);
- console.log("FIRST KEY PHRASE: ", props.keyphrases[0]);
}
render() {
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 2ed528836..9fad612ee 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,4 +1,8 @@
import { library } from '@fortawesome/fontawesome-svg-core';
+
+import {
+ faHireAHelper
+} from '@fortawesome/free-brands-svg-icons';
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,
@@ -6,7 +10,8 @@ import {
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
- faHeading, faRulerCombined, faFillDrip, faUnlink
+ faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+ faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser
} from '@fortawesome/free-solid-svg-icons';
import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -36,7 +41,6 @@ import SharingManager from '../util/SharingManager';
import { Transform } from '../util/Transform';
import { CollectionDockingView } from './collections/CollectionDockingView';
import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import InkOptionsMenu from './collections/collectionFreeForm/InkOptionsMenu';
import { CollectionLinearView } from './collections/CollectionLinearView';
import { CollectionView, CollectionViewType } from './collections/CollectionView';
import { ContextMenu } from './ContextMenu';
@@ -60,10 +64,11 @@ import { DocumentManager } from '../util/DocumentManager';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { LinkMenu } from './linking/LinkMenu';
import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
+import CollectionMenu from './collections/CollectionMenu';
@observer
export class MainView extends React.Component {
@@ -88,7 +93,7 @@ export class MainView extends React.Component {
public isPointerDown = false;
componentDidMount() {
- DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
+ DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
const tag = document.createElement('script');
@@ -147,7 +152,8 @@ export class MainView extends React.Component {
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
- faHeading, faRulerCombined, faFillDrip, faUnlink);
+ faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+ faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser, faHireAHelper);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -388,7 +394,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) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
@computed get flyout() {
@@ -454,9 +460,6 @@ export class MainView extends React.Component {
<button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
<FontAwesomeIcon icon="cog" size="lg" />
</button>
- <button className="mainView-settings" key="groups" onClick={() => GroupManager.Instance.open()}>
- <FontAwesomeIcon icon="columns" size="lg" />
- </button>
</div>
</div>
{this.docButtons}
@@ -465,11 +468,13 @@ export class MainView extends React.Component {
@computed get mainContent() {
const sidebar = this.userDoc?.["tabs-panelContainer"];
+ const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
+ const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
<div className="mainView-mainContent" style={{
color: this.darkScheme ? "rgb(205,205,205)" : "black",
//change to times 2 for both pinned
- height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? (RichTextMenu.Instance?.Pinned && InkOptionsMenu.Instance?.Pinned) ? `calc(100% - 2*${ANTIMODEMENU_HEIGHT})` : `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%",
+ height,
width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%"
}} >
<div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
@@ -609,7 +614,7 @@ export class MainView extends React.Component {
<GoogleAuthenticationManager />
<HypothesisAuthenticationManager />
<DocumentDecorations />
- <InkOptionsMenu />
+ <CollectionMenu />
<FormatShapePane />
<RichTextMenu key="rich" />
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
@@ -621,7 +626,7 @@ export class MainView extends React.Component {
{this.mainContent}
</GestureOverlay>
<PreviewCursor />
- <LinkCreatedBox />
+ <TaskCompletionBox />
<ContextMenu />
<FormatShapePane />
<RadialMenu />
diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss
index f5a9ee76c..812fe540b 100644
--- a/src/client/views/MainViewModal.scss
+++ b/src/client/views/MainViewModal.scss
@@ -6,9 +6,10 @@
align-self: center;
align-content: center;
padding: 20px;
- background: gainsboro;
+ // background: gainsboro;
+ background: white;
border-radius: 10px;
- border: 3px solid black;
+ border: 0.5px solid black;
box-shadow: #00000044 5px 5px 10px;
transform: translate(-50%, -50%);
top: 50%;
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index a7bd5882d..249715511 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import "./MainViewModal.scss";
+import { observer } from 'mobx-react';
export interface MainViewOverlayProps {
isDisplayed: boolean;
@@ -9,8 +10,10 @@ export interface MainViewOverlayProps {
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
overlayDisplayedOpacity?: number;
+ closeOnExternalClick?: () => void;
}
+@observer
export default class MainViewModal extends React.Component<MainViewOverlayProps> {
render() {
@@ -18,11 +21,10 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
return !p.isDisplayed ? (null) : (
- <div style={{ pointerEvents: p.isDisplayed ? p.interactive ? "all" : "none" : "none" }}>
+ <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}>
<div
className={"dialogue-box"}
style={{
- backgroundColor: "gainsboro",
borderColor: "black",
...(p.dialogueBoxStyle || {}),
opacity: p.isDisplayed ? dialogueOpacity : 0
@@ -30,6 +32,7 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
>{p.contents}</div>
<div
className={"overlay"}
+ onClick={this.props?.closeOnExternalClick}
style={{
backgroundColor: "gainsboro",
...(p.overlayStyle || {}),
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index b0752ffb2..82ec5a5b3 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;
}
@@ -38,27 +38,14 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
let field: Field | undefined | null = null;
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";
- }
+ const docs = this.props.docs;
+ 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 docs = this.props.docs;
+ 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/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 6583589f3..b4116e980 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -69,11 +69,11 @@ export class PreviewCursor extends React.Component<{}> {
const list: Doc[] = [];
let first: Doc | undefined;
- docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => {
+ docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
count++;
if (doc instanceof Doc) {
i === 1 && (first = doc);
- const alias = clone ? Doc.MakeClone(doc) : doc;
+ const alias = clone ? (await Doc.MakeClone(doc)).clone : doc;
const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx;
const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty;
alias.x = newPoint[0] + deltaX;
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 888f84dfa..2c185be86 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -39,7 +39,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
@action
onError = (error: string) => {
- console.log(error);
+ console.log("ScriptBox: " + error);
}
overlayDisposer?: () => void;
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
index e038d8213..084f952a3 100644
--- a/src/client/views/SearchDocBox.tsx
+++ b/src/client/views/SearchDocBox.tsx
@@ -60,8 +60,6 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
componentDidMount() {
runInAction(() => {
- console.log("didit"
- );
this.query = StrCast(this.props.Document.searchText);
this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }));
@@ -83,12 +81,9 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
if (newKey.length > 1) {
const newdocs = await this.getAllResults(this.query);
const things = newdocs.docs;
- console.log(things);
- console.log(this.content);
runInAction(() => {
this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query });
});
- console.log(this.content);
}
@@ -150,12 +145,9 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
}
enter = async (e: React.KeyboardEvent) => {
- console.log(e.key);
if (e.key === "Enter") {
const newdocs = await this.getAllResults(this.query);
- console.log(newdocs.docs);
this.content = Docs.Create.TreeDocument(newdocs.docs, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` });
-
}
}
@@ -256,7 +248,6 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
startDragCollection = async () => {
const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString));
const filtered = FilterBox.Instance.filterDocsByType(res.docs);
- // console.log(this._results)
const docs = filtered.map(doc => {
const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
if (isProto) {
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/Touchable.tsx b/src/client/views/Touchable.tsx
index 5e48d5ffb..c4cae7e8d 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -54,7 +54,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
}
});
- // console.log(ptsToDelete.length);
ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
if (this.prevPoints.size) {
@@ -86,7 +85,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
// if we're not actually moving a lot, don't consider it as dragging yet
if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
this._touchDrag = true;
- // console.log(myTouches.length);
switch (myTouches.length) {
case 1:
this.handle1PointerMove(te, me);
@@ -107,7 +105,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
@action
protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up");
// remove all the touches associated with the event
const te = me.touchEvent;
for (const pt of me.changedTouches) {
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 3a7182a94..1b81c544a 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -527,10 +527,6 @@ export class Keyframe extends React.Component<IProps> {
*/
//154, 206, 223
render() {
- trace();
- console.log(this.props.RegionData.position);
- console.log(this.regiondata.position);
- console.log(this.pixelPosition);
return (
<div className="bar" ref={this._bar} style={{
transform: `translate(${this.pixelPosition}px)`,
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index eac30ca75..df828897e 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -466,7 +466,6 @@ export class Timeline extends React.Component<FieldViewProps> {
//TODO: remove undefineds and duplicates
}
});
- // console.log(longestTime);
return longestTime;
}
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 4987006e7..25c2e68e7 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -134,7 +134,6 @@ export class Track extends React.Component<IProps> {
autoCreateKeyframe = () => {
const objects = this.objectWhitelist.map(key => this.props.node[key]);
intercept(this.props.node, change => {
- console.log(change);
return change;
});
return reaction(() => {
@@ -174,7 +173,6 @@ export class Track extends React.Component<IProps> {
if (regiondata) {
this.props.node.hidden = false;
// if (!this._autoKfReaction) {
- // // console.log("creating another reaction");
// // this._autoKfReaction = this.autoCreateKeyframe();
// }
this.timeChange();
@@ -204,7 +202,6 @@ export class Track extends React.Component<IProps> {
}
});
} else {
- console.log("reverting state");
//this.revertState();
}
});
@@ -229,7 +226,6 @@ export class Track extends React.Component<IProps> {
const rightkf: (Doc | undefined) = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists
const currentkf: (Doc | undefined) = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
if (currentkf) {
- console.log("is current");
await this.applyKeys(currentkf);
this.saveStateKf = currentkf;
this.saveStateRegion = regiondata;
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 8f1cd5311..0f3b6f212 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}
@@ -86,7 +86,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
interval?: number;
startAutoScroll = (direction: number) => {
this.interval = window.setInterval(() => {
- console.log(this.interval, this.scrollSpeed);
this.changeSlide(direction);
}, this.scrollSpeed);
}
@@ -113,13 +112,11 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
onPointerDown = (e: React.PointerEvent) => {
this._downX = e.clientX;
this._downY = e.clientY;
- console.log("CAROUSEL down");
document.addEventListener("pointerup", this.onpointerup);
}
private _lastTap: number = 0;
private _doubleTap = false;
onpointerup = (e: PointerEvent) => {
- console.log("CAROUSEL up");
this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
this._lastTap = Date.now();
}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index b3fecfb91..27aea4b99 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}
@@ -88,13 +91,11 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
onPointerDown = (e: React.PointerEvent) => {
this._downX = e.clientX;
this._downY = e.clientY;
- console.log("CAROUSEL down");
document.addEventListener("pointerup", this.onpointerup);
}
private _lastTap: number = 0;
private _doubleTap = false;
onpointerup = (e: PointerEvent) => {
- console.log("CAROUSEL up");
this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
this._lastTap = Date.now();
}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 18d642510..1895c06a1 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,5 +1,23 @@
@import "../../views/globalCssVariables.scss";
+.miniMap {
+ position: absolute;
+ overflow: hidden;
+ right: 10;
+ bottom: 10;
+ border: solid 1px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+
+ .miniOverlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ .miniThumb {
+ background: #25252525;
+ position: absolute;
+ }
+ }
+}
.lm_title {
margin-top: 3px;
border-radius: 5px;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f408e24a8..53b2d5254 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { FieldId } from "../../../fields/RefField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse, emptyPath, aggregateBounds } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
@@ -28,6 +28,9 @@ import { DockingViewButtonSelector } from './ParentDocumentSelector';
import React = require("react");
import { CollectionViewType } from './CollectionView';
import { SnappingManager } from '../../util/SnappingManager';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { listSpec } from '../../../fields/Schema';
+import { clamp } from 'lodash';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -436,7 +439,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
@@ -662,7 +665,6 @@ interface DockedFrameProps {
documentId: FieldId;
glContainer: any;
libraryPath: (FieldId[]);
- backgroundColor?: (doc: Doc) => string | undefined;
//collectionDockingView: CollectionDockingView
}
@observer
@@ -816,35 +818,109 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
+ @computed get renderContentBounds() {
+ const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+ const xbounds = bounds[2] - bounds[0];
+ const ybounds = bounds[3] - bounds[1];
+ const dim = Math.max(xbounds, ybounds);
+ return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+ }
+ @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
+ @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
+ @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
+ miniDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+ this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
+ this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
+ return false;
+ }), emptyFunction, emptyFunction);
+ }
+ renderMiniMap() {
+ return <div className="miniMap" style={{
+ width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
+ StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!))),
+ }}>
+ <CollectionFreeFormView
+ Document={this._document!}
+ LibraryPath={emptyPath}
+ CollectionView={undefined}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ChildLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid havin to set stuff like this.
+ noOverlay={true} // don't render overlay Docs since they won't scale
+ active={returnTrue}
+ select={emptyFunction}
+ dropAction={undefined}
+ isSelected={returnFalse}
+ dontRegisterView={true}
+ annotationsKey={""}
+ fieldKey={Doc.LayoutFieldKey(this._document!)}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ ContentScaling={returnOne}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
+ fitToBox={true}
+ />
+ <div className="miniOverlay" onPointerDown={this.miniDown} >
+ <div className="miniThumb" style={{
+ width: `${this.miniWidth}%`,
+ height: `${this.miniHeight}%`,
+ left: `${this.miniLeft}%`,
+ top: `${this.miniTop}%`,
+ }}
+ />
+ </div>
+ </div>;
+ }
@computed get docView() {
TraceMobx();
if (!this._document) return (null);
const document = this._document;
- const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;// document.layout instanceof Doc ? document : this._dataDoc;
- return <DocumentView key={document[Id]}
- LibraryPath={this._libraryPath}
- Document={document}
- DataDoc={resolvedDataDoc}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={undefined}
- removeDocument={undefined}
- ContentScaling={this.contentScaling}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- NativeHeight={this.nativeHeight}
- NativeWidth={this.nativeWidth}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- renderDepth={0}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- focus={emptyFunction}
- backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
- addDocTab={this.addDocTab}
- pinToPres={DockedFrameRenderer.PinDoc}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />;
+ const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
+ return <>
+ <DocumentView key={document[Id]}
+ LibraryPath={this._libraryPath}
+ Document={document}
+ DataDoc={resolvedDataDoc}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ NativeHeight={this.nativeHeight}
+ NativeWidth={this.nativeWidth}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+ </>;
}
render() {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index dd4df20c9..407524353 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -124,7 +124,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
return <div className="collectionLinearView-outer">
<div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <Tooltip title={BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"} placement="top">
+ <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}
@@ -176,17 +176,20 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.StartLink.title} </span>
- <Tooltip title={LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
- "Turn on description pop-up"} placement="top">
- <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}
- > Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ 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="Exit link clicking mode" placement="top">
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}
- >Clear</span>
+ <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" }}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 627b22417..9a7ea2c93 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -84,7 +84,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@undoBatch
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
- console.log("masronry row drop");
this._createAliasSelected = false;
if (de.complete.docDragData) {
(this.props.parent.Document.dropConverter instanceof ScriptField) &&
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionMenu.scss
index b1e8d20ad..9da204787 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,35 +1,47 @@
@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: #323232;
+ color: white;
transform-origin: top left;
+ top: 0;
+ width:100%;
+
+ .antimodeMenu-button {
+ padding: 0;
+ width: 30px;
+ 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;
.collectionViewBaseChrome-viewPicker {
font-size: 75%;
- //text-transform: uppercase;
- //letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #323232;
outline-color: black;
+ color: white;
border: none;
- //padding: 12px 10px 11px 10px;
+ border-right: solid gray 1px;
}
.collectionViewBaseChrome-viewPicker:active {
@@ -52,21 +64,22 @@
margin-left: 3px;
margin-right: 0px;
font-size: 75%;
- background: rgb(238, 238, 238);
+ background: #323232;
+ color: white;
border: none;
- color: grey;
+ border-right: solid gray 1px;
}
.commandEntry-outerDiv {
pointer-events: all;
- background-color: gray;
+ background-color: #323232;
display: flex;
flex-direction: row;
height: 30px;
.commandEntry-drop {
color: white;
- width: 25px;
+ width: 30px;
margin-top: auto;
margin-bottom: auto;
}
@@ -75,7 +88,7 @@
.collectionViewBaseChrome-collapse {
transition: all .5s, opacity 0.3s;
position: absolute;
- width: 40px;
+ width: 30px;
transform-origin: top left;
pointer-events: all;
// margin-top: 10px;
@@ -94,27 +107,25 @@
color: grey;
margin-top: auto;
margin-bottom: auto;
- margin-left: 5px;
}
-
- .collectionViewBaseChrome-viewModes {
- margin-left: 25px;
- }
-
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
+ border: none;
+ border-right: solid gray 1px;
.collectionViewBaseChrome-filterIcon {
position: relative;
display: flex;
margin: auto;
- background: gray;
+ background: #323232;
color: white;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
+ border: none;
+ border-right: solid gray 1px;
}
.collectionViewBaseChrome-viewSpecsInput {
@@ -190,11 +201,12 @@
font-size: 75%;
//text-transform: uppercase;
//letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #121721;
+ color: white;
outline-color: black;
+ color: white;
border: none;
- //padding: 12px 10px 11px 10px;
+ border-right: solid gray 1px;
}
.collectionGridViewChrome-viewPicker:active {
@@ -297,29 +309,55 @@
}
}
-.collectionFreeFormViewChrome-cont {
- width: 60px;
- display: flex;
+.collectionFreeFormMenu-cont {
+ display: inline-flex;
position: relative;
align-items: center;
+ .antimodeMenu-button {
+ text-align: center;
+ display: block;
+ }
+ .color-previewI {
+ width: 80%;
+ height: 20%;
+ bottom: 0;
+ position: absolute;
+ }
+ .color-previewII {
+ width: 80%;
+ height: 80%;
+ margin-left: 10%;
+ }
+
+ .btn-group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ margin: auto;
+ /* Make the buttons appear below each other */
+ }
+
+ .btn-draw {
+ display: inline-flex;
+ margin: auto;
+ /* Make the buttons appear below each other */
+ }
+
.fwdKeyframe,
.numKeyframe,
.backKeyframe {
cursor: pointer;
- position: absolute;
+ position: relative;
width: 20;
height: 30;
bottom: 0;
- background: gray;
- display: flex;
+ background: #323232;
+ display: inline-flex;
align-items: center;
color: white;
}
.backKeyframe {
- left: 0;
-
svg {
display: block;
margin: auto;
@@ -327,19 +365,16 @@
}
.numKeyframe {
- left: 20;
- display: flex;
flex-direction: column;
- padding: 5px;
+ padding-top: 5px;
}
.fwdKeyframe {
- left: 40;
-
svg {
display: block;
margin: auto;
}
+ border-right: solid gray 1px;
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionMenu.tsx
index 7f1fe7649..0b30b5a5f 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,96 +1,153 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, Lambda } from "mobx";
+import React = require("react");
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+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 { Doc, DocListCast, Opt, Field } from "../../../fields/Doc";
+import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
+import AntimodeMenu from "../AntimodeMenu";
+import "./CollectionMenu.scss";
import { undoBatch } from "../../util/UndoManager";
-import { 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";
+import FormatShapePane from "./collectionFreeForm/FormatShapePane";
+import { ActiveFillColor, SetActiveInkWidth, ActiveInkColor, SetActiveBezierApprox, SetActiveArrowEnd, SetActiveArrowStart, SetActiveFillColor, SetActiveInkColor } from "../InkingStroke";
+import GestureOverlay from "../GestureOverlay";
+import { InkTool } from "../../../fields/InkField";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Document } from "../../../fields/documentSchemas";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentView } from "../nodes/DocumentView";
+import { ColorState } from "react-color";
+import { ObjectField } from "../../../fields/ObjectField";
-interface CollectionViewChromeProps {
- CollectionView: CollectionView;
- type: CollectionViewType;
- collapse?: (value: boolean) => any;
- PanelWidth: () => number;
+@observer
+export default class CollectionMenu extends AntimodeMenu {
+ static Instance: CollectionMenu;
+
+ @observable SelectedCollection: DocumentView | undefined;
+ @observable FieldKey: string;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ this.FieldKey = "";
+ CollectionMenu.Instance = this;
+ this._canFade = false; // don't let the inking menu fade away
+ this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
+ this.jumpTo(300, 300);
+ }
+
+ componentDidMount() {
+ reaction(() => SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0],
+ (doc) => doc && this.SetSelection(doc))
+ }
+
+ @action
+ SetSelection(view: DocumentView) {
+ this.SelectedCollection = view;
+ }
+
+ @action
+ toggleMenuPin = (e: React.MouseEvent) => {
+ Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+ 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"
+ docView={this.SelectedCollection}
+ fieldKey={Doc.LayoutFieldKey(this.SelectedCollection?.props.Document)}
+ type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
+ button]);
+ }
}
-interface Filter {
- key: string;
- value: string;
- contains: boolean;
+interface CollectionMenuProps {
+ type: CollectionViewType;
+ fieldKey: string;
+ docView: DocumentView;
}
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; }
+ get document() { return this.props.docView?.props.Document; }
+ get target() { return this.document; }
_templateCommand = {
- params: ["target", "source"], title: "=> item view",
- script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
- immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
+ params: ["target", "source"], title: "item view",
+ script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
+ immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_narrativeCommand = {
- params: ["target", "source"], title: "=> child click view",
- script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+ params: ["target", "source"], title: "child click view",
+ script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])",
immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_contentCommand = {
- params: ["target", "source"], title: "=> clear content",
- script: "getProto(this.target).data = copyField(this.source);",
+ params: ["target", "source"], title: "clear content",
+ script: "getProto(self.target).data = copyField(self.source);",
immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
initialize: emptyFunction,
};
_viewCommand = {
- params: ["target"], title: "=> reset view",
- script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
- immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
- initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
+ params: ["target"], title: "bookmark view",
+ script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];",
+ immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
+ initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
};
_clusterCommand = {
- params: ["target"], title: "=> fit content",
- script: "this.target._fitToBox = !this.target._fitToBox;",
+ params: ["target"], title: "fit content",
+ script: "self.target._fitToBox = !self.target._fitToBox;",
immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
initialize: emptyFunction
};
_fitContentCommand = {
- params: ["target"], title: "=> toggle clusters",
- script: "this.target.useClusters = !this.target.useClusters;",
+ params: ["target"], title: "toggle clusters",
+ script: "self.target.useClusters = !self.target.useClusters;",
immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
initialize: emptyFunction
};
+ _saveFilterCommand = {
+ params: ["target"], title: "save filter",
+ script: "self.target._docFilters = copyField(self['target-docFilters']);",
+ immediate: undoBatch((source: Doc[]) => this.target._docFilters = undefined),
+ initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; },
+ };
- _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+ _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
_stacking_commands = [this._contentCommand, this._templateCommand];
_masonry_commands = [this._contentCommand, this._templateCommand];
_schema_commands = [this._templateCommand, this._narrativeCommand];
_tree_commands = [];
private get _buttonizableCommands() {
switch (this.props.type) {
+ default:
+ case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Tree: return this._tree_commands;
case CollectionViewType.Schema: return this._schema_commands;
case CollectionViewType.Stacking: return this._stacking_commands;
case CollectionViewType.Masonry: return this._stacking_commands;
- case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Time: return this._freeform_commands;
case CollectionViewType.Carousel: return this._freeform_commands;
case CollectionViewType.Carousel3D: return this._freeform_commands;
}
- return [];
}
private _picker: any;
private _commandRef = React.createRef<HTMLInputElement>();
@@ -99,14 +156,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,101 +179,19 @@ 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} />);
- default: return null;
+ default:
+ case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
+ case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" {...this.props} />);
}
}
-
- private get document() {
- return this.props.CollectionView.props.Document;
- }
-
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
@@ -246,15 +213,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
dragViewDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
- const vtype = this.props.CollectionView.collectionViewType;
+ const vtype = this.props.type;
const c = {
params: ["target"], title: vtype,
- script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
- immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
+ script: `this.target._viewType = '${StrCast(this.props.type)}'`,
+ immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
- { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
+ { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
return true;
}, emptyFunction, emptyFunction);
}
@@ -262,7 +229,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
setupMoveUpEvents(this, e, (e, down, delta) => {
this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
- { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+ { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
return true;
}, emptyFunction, () => {
this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
@@ -270,15 +237,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>
+ <button className={"antimodeMenu-button"} >
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
<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,17 +254,16 @@ 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>
+ <button className={"antimodeMenu-button"}>
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
<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)}>
+ value={StrCast(this.props.type)}>
{Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
<option
key={Utils.GenerateGuid()}
@@ -315,34 +279,17 @@ 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-filterIcon" onPointerDown={this.toggleViewSpecs} >
- <FontAwesomeIcon icon="filter" size="2x" />
- </div>
- </div>
+ {this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes}
{this.templateChrome}
+ <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
+ <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
+ <FontAwesomeIcon icon="filter" size="lg" />
+ </button>
+ </div>
</div>
{this.subChrome}
</div>
@@ -352,11 +299,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
-
- get Document() { return this.props.CollectionView.props.Document; }
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean }> {
+ public static Instance: CollectionFreeFormViewChrome;
+ constructor(props: any) {
+ super(props);
+ CollectionFreeFormViewChrome.Instance = this;
+ }
+ get document() { return this.props.docView.props.Document; }
@computed get dataField() {
- return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+ return this.document[Doc.LayoutFieldKey(this.document)];
}
@computed get childDocs() {
return DocListCast(this.dataField);
@@ -364,54 +315,218 @@ 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;
+ this.document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
- this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
- this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+ this.document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.document.lastFrame = Math.max(NumCast(this.document.currentFrame), NumCast(this.document.lastFrame));
}
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.document.currentFrame, "number", null);
if (currentFrame === undefined) {
- this.Document.currentFrame = 0;
+ this.document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
- this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ this.document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ }
+ @undoBatch
+ @action
+ miniMap = (): void => {
+ this.document.hideMinimap = !this.document.hideMinimap;
+ }
+ private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
+ private _width = ["1", "5", "10", "100"];
+ private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
+ private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
+ private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
+ private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
+
+ @observable _shapesNum = this._shape.length;
+ @observable _selected = this._shapesNum;
+
+ @observable _keepMode = false;
+
+ @observable _colorBtn = false;
+ @observable _widthBtn = false;
+ @observable _fillBtn = false;
+
+ @action
+ clearKeep() { this._selected = this._shapesNum; }
+
+ @action
+ changeColor = (color: string, type: string) => {
+ const col: ColorState = {
+ hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+ rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+ };
+ if (type === "color") {
+ SetActiveInkColor(Utils.colorString(col));
+ } else if (type === "fill") {
+ SetActiveFillColor(Utils.colorString(col));
+ }
+ }
+
+ @action
+ editProperties = (value: any, field: string) => {
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+ 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 "dash": doc.strokeDash = value;
+ }
+ }
+ }));
+ }
+
+ @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");
+ }
+ });
+ 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>;
+ }
+
+ @computed get widthPicker() {
+ const 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 =>
+ <button className="antimodeMenu-button" key={wid}
+ onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
+ {wid}
+ </button>)}
+ </div>;
+ }
+
+ @computed get colorPicker() {
+ const 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 =>
+ <button className="antimodeMenu-button" key={color}
+ onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
+ style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
+ {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
+ <div className="color-previewII" style={{ backgroundColor: color }} />
+ </button>)}
+ </div>;
+ }
+ @computed get fillPicker() {
+ const 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 =>
+ <button className="antimodeMenu-button" key={color}
+ onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
+ style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
+ <div className="color-previewII" style={{ backgroundColor: color }}></div>
+ </button>)}
+
+ </div>;
+ }
+
+ @computed get formatPane() {
+ return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
+ onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
+ style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+ <FontAwesomeIcon icon="chevron-right" size="lg" />
+ </button>;
+ }
+
render() {
- return this.Document.isAnnotationOverlay ? (null) :
- <div className="collectionFreeFormViewChrome-cont">
- <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
- </div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
- onClick={action(() => this.Document.editing = !this.Document.editing)} >
- {NumCast(this.Document.currentFrame)}
- </div>
- <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ return <div className="collectionFreeFormMenu-cont">
+ {this.props.docView.props.renderDepth !== 0 ? (null) :
+ <div key="map" title="mini map" className="backKeyframe" onClick={this.miniMap}>
+ <FontAwesomeIcon icon={"map"} size={"lg"} />
</div>
- </div>;
+ }
+ <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
+ onClick={action(() => this.document.editing = !this.document.editing)} >
+ {NumCast(this.document.currentFrame)}
+ </div>
+ <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+
+ {!this.props.isOverlay ? (null) :
+ <button className={"antimodeMenu-button"} key="hypothesis" style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined }} title="Use Hypothesis" onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}>
+ <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} />
+ </button>
+ }
+ {!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating ?
+ <>
+ {this.widthPicker}
+ {this.colorPicker}
+ {this.fillPicker}
+ {this.drawButtons}
+ {this.formatPane}
+ </> :
+ (null)
+ }
+ </div>;
}
}
-
@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionStackingViewChrome extends React.Component<CollectionMenuProps> {
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
- @computed private get descending() { return StrCast(this.props.CollectionView.props.Document._columnsSort) === "descending"; }
- @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
+ get document() { return this.props.docView.props.Document; }
+
+ @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
+ @computed get pivotField() { return StrCast(this.document._pivotField); }
getKeySuggestions = async (value: string): Promise<string[]> => {
value = value.toLowerCase();
- const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+ const docs = DocListCast(this.document[this.props.fieldKey]);
if (docs instanceof Doc) {
return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
} else {
@@ -446,14 +561,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@action
setValue = (value: string) => {
- this.props.CollectionView.props.Document._pivotField = value;
+ this.document._pivotField = value;
return true;
}
@action toggleSort = () => {
- this.props.CollectionView.props.Document._columnsSort =
- this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
- this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+ this.document._columnsSort =
+ this.document._columnsSort === "descending" ? "ascending" :
+ this.document._columnsSort === "ascending" ? undefined : "descending";
}
@action resetValue = () => { this._currentKey = this.pivotField; };
@@ -502,36 +617,37 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
- // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
+ // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ get document() { return this.props.docView.props.Document; }
@undoBatch
togglePreview = () => {
const dividerWidth = 4;
const borderWidth = Number(COLLECTION_BORDER_WIDTH);
- const panelWidth = this.props.CollectionView.props.PanelWidth();
- const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+ const panelWidth = this.props.docView.props.PanelWidth();
+ const previewWidth = NumCast(this.document.schemaPreviewWidth);
const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
- this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+ this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
}
@undoBatch
@action
toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []);
if (textwrappedRows.length) {
- this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+ this.document.textwrappedSchemaRows = new List<string>([]);
} else {
- const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+ const docs = DocListCast(this.document[this.props.fieldKey]);
const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ this.document.textwrappedSchemaRows = new List<string>(allRows);
}
}
render() {
- const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
- const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ const previewWidth = NumCast(this.document.schemaPreviewWidth);
+ const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
return (
<div className="collectionSchemaViewChrome-cont">
@@ -549,13 +665,14 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
}
@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
+ get document() { return this.props.docView.props.Document; }
get sortAscending() {
- return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
+ return this.document[this.props.fieldKey + "-sortAscending"];
}
set sortAscending(value) {
- this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+ this.document[this.props.fieldKey + "-sortAscending"] = value;
}
@computed private get ascending() {
return Cast(this.sortAscending, "boolean", null);
@@ -585,16 +702,17 @@ 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> {
+ get document() { return this.props.docView.props.Document; }
@computed get scrollSpeed() {
- return this.props.CollectionView.props.Document._autoScrollSpeed;
+ return this.document._autoScrollSpeed;
}
@action
setValue = (value: string) => {
const numValue = Number(StrCast(value));
if (numValue > 0) {
- this.props.CollectionView.props.Document._autoScrollSpeed = numValue;
+ this.document._autoScrollSpeed = numValue;
return true;
}
return false;
@@ -624,20 +742,21 @@ 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;
private decrementLimitReached: boolean = false;
@observable private resize = false;
private resizeListenerDisposer: Opt<Lambda>;
+ get document() { return this.props.docView.props.Document; }
componentDidMount() {
- runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+ runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700);
// listener to reduce text on chrome resize (panel resize)
- this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+ this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
runInAction(() => this.resize = newValue < 700);
});
}
@@ -646,7 +765,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
this.resizeListenerDisposer?.();
}
- get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+ get numCols() { return NumCast(this.document.gridNumCols, 10); }
/**
* Sets the value of `numCols` on the grid's Document to the value entered.
@@ -655,7 +774,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === "Tab") {
if (e.currentTarget.valueAsNumber > 0) {
- this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+ this.document.gridNumCols = e.currentTarget.valueAsNumber;
}
}
@@ -667,8 +786,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
// @undoBatch
// onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
// if (e.key === "Enter" || e.key === "Tab") {
- // if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
- // this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+ // if (e.currentTarget.valueAsNumber > 0 && this.document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+ // this.document.rowHeight = e.currentTarget.valueAsNumber;
// }
// }
// }
@@ -678,7 +797,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
*/
@undoBatch
toggleFlex = () => {
- this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+ this.document.gridFlex = !BoolCast(this.document.gridFlex, true);
}
/**
@@ -686,8 +805,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
*/
onIncrementButtonClick = () => {
this.clicked = true;
- this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
- undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+ this.entered && (this.document.gridNumCols as number)--;
+ undoBatch(() => this.document.gridNumCols = this.numCols + 1)();
this.entered = false;
}
@@ -697,8 +816,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
onDecrementButtonClick = () => {
this.clicked = true;
if (!this.decrementLimitReached) {
- this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
- undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+ this.entered && (this.document.gridNumCols as number)++;
+ undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
}
this.entered = false;
}
@@ -709,7 +828,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
incrementValue = () => {
this.entered = true;
if (!this.clicked && !this.decrementLimitReached) {
- this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+ this.document.gridNumCols = this.numCols + 1;
}
this.decrementLimitReached = false;
this.clicked = false;
@@ -722,7 +841,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
this.entered = true;
if (!this.clicked) {
if (this.numCols !== 1) {
- this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+ this.document.gridNumCols = this.numCols - 1;
}
else {
this.decrementLimitReached = true;
@@ -736,7 +855,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
* Toggles the value of preventCollision
*/
toggleCollisions = () => {
- this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+ this.document.gridPreventCollision = !this.document.gridPreventCollision;
}
/**
@@ -744,7 +863,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
*/
changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
// need to change startCompaction so that this operation will be undoable.
- this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+ this.document.gridStartCompaction = e.target.selectedOptions[0].value;
}
render() {
@@ -762,18 +881,18 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
<span className="grid-icon">
<FontAwesomeIcon icon="text-height" size="1x" />
</span>
- <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
</span> */}
<span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
- <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+ <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
<label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
</span>
<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))}>
+ value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}>
{["vertical", "horizontal", "none"].map(type =>
<option className="collectionGridViewChrome-viewOption"
onPointerDown={stopPropagation}
@@ -785,11 +904,11 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
<span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
<input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
- checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+ checked={BoolCast(this.document.gridFlex, true)} />
<label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
</span>
- <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+ <button onClick={() => this.document.gridResetLayout = true}>
{!this.resize ? "Reset" :
<FontAwesomeIcon icon="redo-alt" size="1x" />}
</button>
@@ -797,4 +916,4 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index d76b6d204..eecaf7672 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -151,7 +151,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// let field = this.props.rowProps.original[this.props.rowProps.column.id as string];
// let doc = FieldValue(Cast(field, Doc));
- // console.log("Expanding doc", StrCast(doc!.title));
// this.props.setPreviewDoc(doc!);
// // this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
@@ -265,7 +264,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
return "0";
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
- console.log(cfield);
if (type === "number") {
return StrCast(cfield);
}
@@ -286,7 +284,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
- console.log("compiled");
}
}
@@ -350,17 +347,13 @@ export class CollectionSchemaDateCell extends CollectionSchemaCell {
@action
handleChange = (date: any) => {
- console.log(date);
this._date = date;
// const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
// if (script.compiled) {
- // console.log("scripting");
// this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
// } else {
- console.log(DateCast(date));
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
this._document[this.props.rowProps.column.id as string] = date as Date;
- console.log(this._document[this.props.rowProps.column.id as string]);
//}
}
@@ -425,8 +418,6 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
const results = script.compiled && script.run();
if (results && results.success) {
-
- console.log(results.result);
this._doc = results.result;
this._document[this.prop.fieldKey] = results.result;
this._docTitle = this._doc?.title;
@@ -457,10 +448,8 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
this._preview = false;
} else {
if (bool) {
- console.log("show doc");
this.props.showDoc(this._doc, this.prop.DataDoc, e.clientX, e.clientY);
} else {
- console.log("no doc");
this.props.showDoc(undefined);
}
}
@@ -675,7 +664,6 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
@action
toggleOpened(open: boolean) {
- console.log("open: " + open);
this._opened = open;
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 3c42a2f1c..5553bbbb7 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -332,7 +332,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
openHeader = (col: any, screenx: number, screeny: number) => {
- console.log("header opening");
this._col = col;
this._headerOpen = !this._headerOpen;
this._pointerX = screenx;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9d8790dda..dd4c34885 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -123,7 +123,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
changed = true;
});
}
- changed && setTimeout(action(() => { if (this.columnHeaders) { this.columnHeaders.length = 0; this.columnHeaders.push(...columnHeaders); } }), 0);
+ changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0);
return fields;
}
@@ -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) {
@@ -249,7 +249,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
return wid * aspect;
}
return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
+ Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.max(20, layoutDoc[HeightSym]());
}
columnDividerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2f4a25bfe..76af70cd1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, runInAction, computed } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../fields/Doc";
import { RichTextField } from "../../../fields/RichTextField";
@@ -279,8 +279,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y);
}
-
- render() {
+ @computed get innards() {
TraceMobx();
const cols = this.props.cols();
const key = StrCast(this.props.parent.props.Document._pivotField);
@@ -351,6 +350,43 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div> : (null);
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
const chromeStatus = this.props.parent.props.Document._chromeStatus;
+
+ return <>
+ {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
+ {
+ this.collapsed ? (null) :
+ <div style={{ marginTop: 5 }}>
+ <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
+ style={{
+ padding: singleColumn ? `${columnYMargin}px ${0}px ${style.yMargin}px ${0}px` : `${columnYMargin}px ${0}px`,
+ margin: "auto",
+ width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ height: 'max-content',
+ position: "relative",
+ gridGap: style.gridGap,
+ gridTemplateColumns: singleColumn ? undefined : templatecols,
+ gridAutoRows: singleColumn ? undefined : "0px"
+ }}>
+ {this.props.parent.children(this.props.docList, uniqueHeadings.length)}
+ {singleColumn ? (null) : this.props.parent.columnDragger}
+ </div>
+ {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
+ <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
+ style={{ width: style.columnWidth / style.numGroupColumns }}>
+ <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
+ </div> : null}
+ </div>
+ }
+ </>;
+ }
+
+
+ render() {
+ TraceMobx();
+ const headings = this.props.headings();
+ const heading = this._heading;
+ const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+ const chromeStatus = this.props.parent.props.Document._chromeStatus;
return (
<div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
style={{
@@ -359,31 +395,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
background: this._background
}}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
- {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
- {
- this.collapsed ? (null) :
- <div style={{ marginTop: 5 }}>
- <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
- style={{
- padding: singleColumn ? `${columnYMargin}px ${0}px ${style.yMargin}px ${0}px` : `${columnYMargin}px ${0}px`,
- margin: "auto",
- width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
- height: 'max-content',
- position: "relative",
- gridGap: style.gridGap,
- gridTemplateColumns: singleColumn ? undefined : templatecols,
- gridAutoRows: singleColumn ? undefined : "0px"
- }}>
- {this.props.parent.children(this.props.docList, uniqueHeadings.length)}
- {singleColumn ? (null) : this.props.parent.columnDragger}
- </div>
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
- <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
- style={{ width: style.columnWidth / style.numGroupColumns }}>
- <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
- </div> : null}
- </div>
- }
+ {this.innards}
</div >
);
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3794088d4..d9d9c0eb8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -91,6 +91,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
+ // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly
+ // setTimeout changes it outside of the @computed section
+ setTimeout(() => {
+ if (!this.dataDoc[this.props.annotationsKey || this.props.fieldKey]) this.dataDoc[this.props.annotationsKey || this.props.fieldKey] = new List<Doc>();
+ }, 1000);
return this.dataDoc[this.props.annotationsKey || this.props.fieldKey];
}
@@ -215,6 +220,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const { dataTransfer } = e;
const html = dataTransfer.getData("text/html");
const text = dataTransfer.getData("text/plain");
+ const uriList = dataTransfer.getData("text/uri-list");
if (text && text.startsWith("<div")) {
return;
@@ -286,7 +292,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
- Doc.GetProto(htmlDoc)["data-text"] = text;
+ Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc)["text"] = text;
this.props.addDocument(htmlDoc);
if (srcWeb) {
const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
@@ -294,7 +300,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
const x = (rect?.x || 0);
const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
- const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+ const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 75, _height: 40, x, y, annotationOn: srcWeb });
anchor.context = srcWeb;
const key = Doc.LayoutFieldKey(srcWeb);
Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
@@ -307,9 +313,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
}
- if (text) {
- if (text.includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) {
- const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
+ if (uriList || text) {
+ if ((uriList || text).includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) {
+ const url = (uriList || text).replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
this.addDocument(Docs.Create.VideoDocument(url, {
...options,
title: url,
@@ -334,10 +340,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
// const albumId = matches[3];
// const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
- // console.log(mediaItems);
// return;
// }
}
+ if (uriList) {
+ this.addDocument(Docs.Create.WebDocument(uriList, {
+ ...options,
+ title: uriList,
+ _width: 400,
+ _height: 315,
+ _nativeWidth: 600,
+ _nativeHeight: 472.5
+ }));
+ return;
+ }
const { items } = e.dataTransfer;
const { length } = items;
@@ -364,7 +380,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
file?.type && files.push(file);
file?.type === "application/json" && Utils.readUploadedFileAsText(file).then(result => {
- console.log(result);
const json = JSON.parse(result as string);
this.addDocument(Docs.Create.TreeDocument(
json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => {
@@ -418,4 +433,5 @@ import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTex
import { CollectionView } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
+import { setTimeout } from "timers";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index dbd39d8df..651357e5d 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);
}
@@ -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
@@ -794,8 +796,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
</div >;
}
- onKeyPress = (e: React.KeyboardEvent) => {
- console.log(e);
+ onChildClick = () => {
+ return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
}
render() {
TraceMobx();
@@ -814,7 +816,6 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
paddingTop: `${NumCast(this.doc._yPadding, 20)}px`,
pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
}}
- onKeyPress={this.onKeyPress}
onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
@@ -839,7 +840,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 cbd1ac9af..2c17254ed 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate, AclAdmin } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } from '../../../fields/util';
import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -47,7 +47,8 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import CollectionMenu from './CollectionMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -106,6 +107,14 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ private AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit],
+ [AclAdmin, SharingPermissions.Admin]
+ ]);
+
get collectionViewType(): CollectionViewType | undefined {
const viewField = StrCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
@@ -128,36 +137,54 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
if (this.props.filterAddDocument?.(doc) === false) {
return false;
}
+
const docs = doc instanceof Doc ? [doc] : doc;
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
const added = docs.filter(d => !docList.includes(d));
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+
if (added.length) {
- if (this.dataDoc[AclSym] === AclReadonly) {
+ if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
return false;
- } else if (this.dataDoc[AclSym] === AclAddonly) {
- added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
- } 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)) {
- const pushpin = Docs.Create.FontIconDocument({
- 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)
- });
- 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);
- doc.displayTimecode = undefined;
- }
- doc.context = this.props.Document;
- });
- added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
- targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ else {
+ if (this.props.Document[AclSym]) {
+ // change so it only adds if more restrictive
+ added.forEach(d => {
+ // const dataDoc = d[DataSym];
+ for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+ distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
+ }
+ // dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+ });
+ }
+
+ if (effectiveAcl === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
+ }
+ 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)) {
+ const pushpin = Docs.Create.FontIconDocument({
+ title: "pushpin", label: "",
+ 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", "");
+ doc.displayTimecode = undefined;
+ }
+ doc.context = this.props.Document;
+ });
+ added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
+ targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
}
return true;
@@ -165,13 +192,16 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
- const docs = doc instanceof Doc ? [doc] : doc as Doc[];
- const targetDataDoc = this.props.Document[DataSym];
- const value = DocListCast(targetDataDoc[this.props.fieldKey]);
- const result = value.filter(v => !docs.includes(v));
- if (result.length !== value.length) {
- targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
- return true;
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || getPlaygroundMode()) {
+ const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+ const targetDataDoc = this.props.Document[DataSym];
+ const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+ const result = value.filter(v => !docs.includes(v));
+ if (result.length !== value.length) {
+ targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+ return true;
+ }
}
return false;
}
@@ -234,23 +264,14 @@ 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);
}
setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
- const existingVm = ContextMenu.Instance.findByDescription(category);
- const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
+ const subItems: ContextMenuProps[] = [];
subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
if (addExtras && CollectionView._safeMode) {
ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
@@ -272,31 +293,35 @@ 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, noexpand: true, subitems: subItems, icon: "eye" });
+
+ const existingVm = ContextMenu.Instance.findByDescription(category);
+ const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
+ catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: "eye" });
}
onContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- this.setupViewTypes("Add a Perspective...", vtype => {
+ this.setupViewTypes("UI Controls...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
this.props.addDocTab(newRendition, "onRight");
return newRendition;
}, false);
- const existing = cm.findByDescription("Options...");
- const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
- layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+ const options = cm.findByDescription("Options...");
+ const optionItems = options && "subitems" in options ? options.subitems : [];
+ optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
if (this.props.Document.childLayout instanceof Doc) {
- layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
+ optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
}
if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
- layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
+ optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), 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" });
+ !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
- !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
const existingOnClick = cm.findByDescription("OnClick...");
const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -305,14 +330,16 @@ 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...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
@@ -470,7 +497,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 +551,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 +577,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
+ setTimeout(() => this.props.isSelected(true) && CollectionMenu.Instance.SetSelection(this, this.props.fieldKey), 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/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 649406e6c..8c0b8de9d 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -40,15 +40,21 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
this._reaction?.();
}
async fetchDocuments() {
- const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document)).filter(doc => doc !== this.props.Document);
- const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.Document[Id]}"` });
- const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
- allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
- docs.forEach(doc => map.delete(doc));
+ const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document));
+ const containerProtoSets = await Promise.all(aliases.map(async alias =>
+ await Promise.all((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
+ const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+ const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => {
+ return (await SearchUtil.GetAliasesOfDocument(container));
+ }));
+ const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+ const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => {
+ return (await SearchUtil.GetAliasesOfDocument(dp));
+ }));
+ const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
runInAction(() => {
- this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
- this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+ this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
+ this._otherDocs = [];
});
}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 9e3b4d961..cde795098 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -135,7 +135,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@action
changeSorting = (col: any) => {
- console.log(col.heading);
if (col.desc === undefined) {
// no sorting
this.props.changeColumnSort(col, true);
@@ -149,7 +148,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@action
- changeTitleMode = () => { console.log("header clicked"); this._showTitleDropdown = !this._showTitleDropdown; }
+ changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown;
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@computed get tableColumns(): Column<Doc>[] {
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 ae79c27e0..bfe569853 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,13 +1,12 @@
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 { observable, action, reaction, IReactionDisposer, trace, computed } from "mobx";
+import { StrCast, Cast, NumCast } from "../../../../fields/Types";
import { Id } from "../../../../fields/FieldSymbols";
import { SnappingManager } from "../../../util/SnappingManager";
@@ -20,18 +19,27 @@ export interface CollectionFreeFormLinkViewProps {
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
@observable _opacity: number = 0;
+ @observable _start = 0;
_anchorDisposer: IReactionDisposer | undefined;
+ _timeout: NodeJS.Timeout | undefined;
+ componentWillUnmount() {
+ this._anchorDisposer?.();
+ }
@action
+ timeout = () => (Date.now() < this._start++ + 1000) && setTimeout(this.timeout, 25)
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;
+ this._start = Date.now();
+ this._timeout && clearTimeout(this._timeout);
+ this._timeout = setTimeout(this.timeout, 25);
+ 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,17 +90,31 @@ 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;
+
+ @computed get renderData() {
+ this._start;
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) {
+ return undefined;
+ }
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");
- const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
- const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
+ const acont = this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const a = (acont.length ? acont[0] : this.props.A.ContentDiv).getBoundingClientRect();
+ const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv).getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
b.left, b.top, b.width, b.height,
a.left + a.width / 2, a.top + a.height / 2);
@@ -105,18 +127,26 @@ 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.description);
- 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}
- </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, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 };
+ }
+
+ render() {
+ if (!this.renderData) return (null);
+ const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
+ 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 ef6ea8f99..daa6e7440 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -46,6 +46,8 @@ import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { SearchUtil } from "../../../util/SearchUtil";
+import { LinkManager } from "../../../util/LinkManager";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -75,7 +77,8 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
- scaleField?: string;
+ scaleField?: string; // used by formattedTextBox when displaying a sidebar freeform view which needs its own scale field
+ noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
};
@observer
@@ -84,8 +87,6 @@ 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>();
@@ -186,7 +187,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
@@ -496,10 +497,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
this._inkToTextStartX = start[0];
this._inkToTextStartY = start[1];
- console.log("start");
break;
case GestureUtils.Gestures.EndBracket:
- console.log("end");
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color);
@@ -581,7 +580,6 @@ 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);
@@ -939,22 +937,26 @@ 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 {
return {
- ...this.props,
+ addDocument: this.props.addDocument,
+ removeDocument: this.props.removeDocument,
+ moveDocument: this.props.moveDocument,
+ pinToPres: this.props.pinToPres,
+ whenActiveChanged: this.props.whenActiveChanged,
NativeHeight: returnZero,
NativeWidth: returnZero,
fitToBox: false,
DataDoc: childData,
Document: childLayout,
LibraryPath: this.libraryPath,
- LayoutTemplate: this.props.ChildLayoutTemplate,
- LayoutTemplateString: this.props.ChildLayoutString,
+ LayoutTemplate: childLayout.z ? undefined : this.props.ChildLayoutTemplate,
+ LayoutTemplateString: childLayout.z ? undefined : this.props.ChildLayoutString,
FreezeDimensions: this.props.freezeChildDimensions,
layoutKey: undefined,
setupDragLines: this.setupDragLines,
@@ -1147,20 +1149,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
componentDidMount() {
super.componentDidMount?.();
- this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
+ this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation },
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
- const handler = (e: Event) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
-
- document.addEventListener("dashDragging", handler);
+ this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
componentWillUnmount() {
this._layoutComputeReaction?.();
-
- const handler = (e: Event) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
- document.removeEventListener("dashDragging", handler);
+ this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
@@ -1175,39 +1173,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// <div ref={this._marqueeRef}>
@action
- handleDragging = (e: CustomEvent<React.DragEvent>, de: DragEvent) => {
- if ((e as any).handlePan) return;
+ onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ if ((e as any).handlePan || this.props.isAnnotationOverlay) 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();
- let deltaX = dragX - bounds.left < 25 ? -2 : bounds.right - dragX < 25 ? 2 : 0;
- let deltaY = dragY - bounds.top < 25 ? -2 : bounds.bottom - dragY < 25 ? 2 : 0;
- (deltaX !== 0 || deltaY !== 0) && this.continuePan(deltaX, deltaY);
+ const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
+ const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
+ if (deltaX !== 0 || deltaY !== 0) {
+ this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
+ this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+ }
}
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 => {
@@ -1258,55 +1242,69 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const appearance = ContextMenu.Instance.findByDescription("Appearance...");
const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
- appearanceItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
- appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+ appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
+ appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
!appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+ const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
+ const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
+ viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
+ viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+ !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
+
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems = options && "subitems" in options ? options.subitems : [];
- !this.props.isAnnotationOverlay &&
+ !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
this.props.ContainingCollectionView &&
optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
- optionItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " snap lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
- optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+ appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
if (!Doc.UserDoc().noviceMode) {
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
- optionItems.push({
- description: "Import document", icon: "upload", event: ({ x, y }) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = ".zip";
- input.onchange = async _e => {
- const upload = Utils.prepend("/uploadDoc");
- const formData = new FormData();
- const file = input.files && input.files[0];
- if (file) {
- formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json !== "error") {
- const doc = await DocServer.GetRefField(json);
- if (doc instanceof Doc) {
- const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
- doc.x = xx, doc.y = yy;
- this.props.addDocument?.(doc);
- }
- }
- }
- };
- input.click();
- }
- });
+
}
!options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
-
+ const mores = ContextMenu.Instance.findByDescription("More...");
+ const moreItems = mores && "subitems" in mores ? mores.subitems : [];
+ moreItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+ !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
+ }
+
+ importDocument = (x: number, y: number) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".zip";
+ input.onchange = async _e => {
+ const upload = Utils.prepend("/uploadDoc");
+ const formData = new FormData();
+ const file = input.files && input.files[0];
+ if (file) {
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json !== "error") {
+ const doc = await DocServer.GetRefField(json);
+ if (doc instanceof Doc) {
+ const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
+ doc.x = xx, doc.y = yy;
+ this.props.addDocument?.(doc);
+ setTimeout(() => {
+ SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
+ docs.docs.forEach(d => LinkManager.Instance.addLink(d));
+ })
+ }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
+ }
+ }
+ }
+ };
+ input.click();
}
+
+
@observable showTimeline = false;
intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -1425,6 +1423,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
+ !this.fitToContent && this._layoutElements?.length && setTimeout(() => this.Document._renderContentBounds = new List<number>([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0);
return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
onPointerOver={this.onPointerOver}
onWheel={this.onPointerWheel}
@@ -1443,7 +1442,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}}>
{this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
this.placeholder : this.marqueeView}
- <CollectionFreeFormOverlayView elements={this.elementFunc} />
+ {!this.props.noOverlay ? <CollectionFreeFormOverlayView elements={this.elementFunc} /> : (null)}
<div className={"pullpane-indicator"}
style={{
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index a2600a2b3..cd5ca7fd4 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -35,7 +35,7 @@ export default class FormatShapePane extends AntimodeMenu {
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>)
+ (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() {
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
deleted file mode 100644
index 03c6c97ab..000000000
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ /dev/null
@@ -1,83 +0,0 @@
-.antimodeMenu-button {
- .color-previewI {
- width: 100%;
- height: 40%;
- }
-
- .color-previewII {
- width: 100%;
- height: 100%;
- }
-
-}
-
-.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-draw {
- display: inline;
- /* Make the buttons appear below each other */
-}
-
-.btn2-group {
- display: grid;
- background: #323232;
- grid-template-columns: auto;
-
- /* Make the buttons appear below each other */
- .antimodeMenu-button {
- background: #323232;
- display: block;
-
- }
-}
-
-@media only screen and (max-device-width: 480px) {
- .antimodeMenu-button {
- font-size: 50%;
-
- .color-preview {
- width: 100%;
- height: 100%;
- }
-
- }
-
- .sketch-picker {
- background: #323232;
-
- .flexbox-fit {
- background: #323232;
- }
- }
-
- .btn-group {
- display: grid;
- grid-template-columns: auto auto;
- /* 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;
- font-size: 50%;
- }
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2db665337..764758eee 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
+import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -188,15 +189,18 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.clientX;
this._downY = this._lastY = e.clientY;
- // allow marquee if right click OR alt+left click OR space bar + left click
- if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
- // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
- this.setPreviewCursor(e.clientX, e.clientY, true);
- // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
- e.preventDefault();
- // }
- // bcz: do we need this? it kills the context menu on the main collection if !altKey
- // e.stopPropagation();
+ if (!(e.nativeEvent as any).marqueeHit) {
+ (e.nativeEvent as any).marqueeHit = true;
+ // allow marquee if right click OR alt+left click OR space bar + left click
+ if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
+ // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
+ this.setPreviewCursor(e.clientX, e.clientY, true);
+ // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
+ e.preventDefault();
+ // }
+ // bcz: do we need this? it kills the context menu on the main collection if !altKey
+ // e.stopPropagation();
+ }
}
}
@@ -276,7 +280,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl) || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
this.clearSelection();
}
});
@@ -286,7 +291,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
if (Doc.GetSelectedTool() === InkTool.None) {
- !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
+ if (!(e.nativeEvent as any).marqueeHit) {
+ (e.nativeEvent as any).marqueeHit = true;
+ !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
+ }
}
// let the DocumentView stopPropagation of this event when it selects this document
} else { // why do we get a click event when the cursor have moved a big distance?
@@ -426,8 +434,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
// const wordResults = results.filter((r: any) => r.category === "inkWord");
- // console.log(wordResults);
- // console.log(results);
// for (const word of wordResults) {
// const indices: number[] = word.strokeIds;
// indices.forEach(i => {
@@ -468,7 +474,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// });
// }
const lines = results.filter((r: any) => r.category === "line");
- console.log(lines);
const text = lines.map((l: any) => l.recognizedText).join("\r\n");
this.props.addDocument(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
});
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index 9c2d5cbff..4d8473be9 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -8,7 +8,6 @@
.collectionGridView-gridContainer {
height: 100%;
overflow-y: auto;
- background-color: white;
overflow-x: hidden;
display: flex;
@@ -22,7 +21,7 @@
}
.react-grid-layout {
- width : 100%;
+ width: 100%;
}
.react-grid-item>.react-resizable-handle {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 188b88c41..21f77e47b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -30,8 +30,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
@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
+ private dropLocation: object = {}; // sets the drop location for external drops
- @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; }
@@ -47,6 +48,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
@computed get flexGrid() { return BoolCast(this.props.Document.gridFlex, true); } // is grid static/flexible i.e. whether nodes be moved around and resized
@computed get compaction() { return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, "vertical")); } // is grid static/flexible i.e. whether nodes be moved around and resized
+ /**
+ * Sets up the listeners for the list of documents and the reset button.
+ */
componentDidMount() {
this._changeListenerDisposer = reaction(() => this.childLayoutPairs, (pairs) => {
const newLayouts: Layout[] = [];
@@ -54,7 +58,15 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
pairs.forEach((pair, i) => {
const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
if (existing) newLayouts.push(existing);
- else this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ else {
+ if (Object.keys(this.dropLocation).length) { // external drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number, y: number }, !this.flexGrid));
+ this.dropLocation = {};
+ }
+ else { // internal drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ }
+ }
});
pairs?.length && this.setLayoutList(newLayouts);
}, { fireImmediately: true });
@@ -68,11 +80,18 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
});
}
+ /**
+ * Disposes the listeners.
+ */
componentWillUnmount() {
this._changeListenerDisposer?.();
this._resetListenerDisposer?.();
}
+ /**
+ * @returns the default location of the grid node (i.e. when the grid is static)
+ * @param index
+ */
unflexedPosition(index: number): Omit<Layout, "i"> {
return {
x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW,
@@ -83,6 +102,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
};
}
+ /**
+ * Maps the x- and y- coordinates of the event to a grid cell.
+ */
screenToCell(sx: number, sy: number) {
const pt = this.props.ScreenToLocalTransform().transformPoint(sx, sy);
const x = Math.floor(pt[0] / this.colWidthPlusGap);
@@ -90,10 +112,16 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
return { x, y };
}
+ /**
+ * Creates a layout object for a grid item
+ */
makeLayoutItem = (doc: Doc, pos: { x: number, y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
return ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
}
+ /**
+ * Adds a layout to the list of layouts.
+ */
addLayoutItem = (layouts: Layout[], layout: Layout) => {
const f = layouts.findIndex(l => l.i === layout.i);
f !== -1 && layouts.splice(f, 1);
@@ -215,6 +243,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index)));
}
+ /**
+ * Handles internal drop of Dash documents.
+ */
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const savedLayouts = this.savedLayoutList;
@@ -228,12 +259,24 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
}
/**
+ * Handles external drop of images/PDFs etc from outside Dash.
+ */
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ this.dropLocation = this.screenToCell(e.clientX, e.clientY);
+ super.onExternalDrop(e, {});
+ }
+
+ /**
* Handles the change in the value of the rowHeight slider.
*/
@action
onSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this._rowHeight = event.currentTarget.valueAsNumber;
}
+ /**
+ * Handles the user clicking on the slider.
+ */
@action
onSliderDown = (e: React.PointerEvent) => {
this._rowHeight = this.rowHeight; // uses _rowHeight during dragging and sets doc's rowHeight when finished so that operation is undoable
@@ -252,6 +295,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
}
+ /**
+ * Handles text document creation on double click.
+ */
onPointerDown = (e: React.PointerEvent) => {
if (this.props.active(true)) {
setupMoveUpEvents(this, e, returnFalse, returnFalse,
@@ -275,8 +321,11 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
<div className="collectionGridView-contents" ref={this.createDashEventsTarget}
style={{ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined }}
onContextMenu={this.onContextMenu}
- onPointerDown={e => this.onPointerDown(e)} >
+ onPointerDown={this.onPointerDown}
+ onDrop={this.onExternalDrop}
+ >
<div className="collectionGridView-gridContainer" ref={this._containerRef}
+ style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, "white") }}
onWheel={e => e.stopPropagation()}
onScroll={action(e => {
if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index cd25c21b4..21d283547 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -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 51dcdcfe6..d02088a6c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -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 87afc99eb..d26b7920a 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -13,6 +13,10 @@
width: 18px;
height: 18px;
padding: 0;
+
+ &:hover {
+ cursor: pointer;
+ }
}
.linkEditor-info {
@@ -28,7 +32,13 @@
.linkEditor-linkedTo {
width: calc(100% - 26px);
padding-left: 5px;
- padding-right: 5px
+ padding-right: 5px;
+
+ .linkEditor-downArrow {
+ &:hover {
+ cursor: pointer;
+ }
+ }
}
}
@@ -43,6 +53,10 @@
.button {
color: black;
+
+ &:hover {
+ cursor: pointer;
+ }
}
}
@@ -83,6 +97,10 @@
padding-right: 8px;
height: 80%;
color: white;
+
+ &:hover {
+ cursor: pointer;
+ }
}
}
}
@@ -92,6 +110,10 @@
padding-right: 6.5px;
padding-bottom: 6px;
+ &:hover {
+ cursor: pointer;
+ }
+
.linkEditor-followingDropdown-label {
color: black;
}
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index a26685318..04329182e 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -13,6 +13,8 @@ import { DocumentView } from "../nodes/DocumentView";
import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
import { EditableView } from "../EditableView";
import { RefObject } from "react";
+import { Tooltip } from "@material-ui/core";
+import { undoBatch } from "../../util/UndoManager";
library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
@@ -285,19 +287,16 @@ interface LinkEditorProps {
@observer
export class LinkEditor extends React.Component<LinkEditorProps> {
-
@observable description = StrCast(LinkManager.currentLink?.description);
@observable openDropdown: boolean = false;
-
- @observable followBehavior = this.props.linkDoc.follow ? this.props.linkDoc.follow : "Default";
-
@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";
+ @undoBatch
@action
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
@@ -308,6 +307,10 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
setDescripValue = (value: string) => {
if (LinkManager.currentLink) {
LinkManager.currentLink.description = value;
+ this.buttonColor = "rgb(62, 133, 55)";
+ setTimeout(action(() => {
+ this.buttonColor = "black";
+ }), 750);
return true;
}
}
@@ -349,7 +352,8 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
></input>
</div>
<div className="linkEditor-description-add-button"
- onPointerDown={this.onDown}>Add</div>
+ style={{ backgroundColor: this.buttonColor }}
+ onPointerDown={this.onDown}>Set</div>
</div></div>;
}
@@ -361,8 +365,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@action
changeFollowBehavior = (follow: string) => {
this.openDropdown = false;
- this.followBehavior = follow;
- this.props.linkDoc.follow = follow;
+ Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
}
@computed
@@ -373,7 +376,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-followingDropdown-dropdown">
<div className="linkEditor-followingDropdown-header"
onPointerDown={this.changeDropdown}>
- {this.followBehavior}
+ {StrCast(this.props.linkDoc.followLinkLocation, "Default")}
<FontAwesomeIcon className="linkEditor-followingDropdown-icon"
icon={this.openDropdown ? "chevron-up" : "chevron-down"}
size={"lg"} />
@@ -385,11 +388,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
Default
</div>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("Always open in right tab")}>
+ onPointerDown={() => this.changeFollowBehavior("onRight")}>
Always open in right tab
</div>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("Always open in new tab")}>
+ onPointerDown={() => this.changeFollowBehavior("inTab")}>
Always open in new tab
</div>
</div>
@@ -413,15 +416,17 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
return !destination ? (null) : (
<div className="linkEditor">
<div className="linkEditor-info">
- <button className="linkEditor-button-back"
- style={{ display: this.props.hideback ? "none" : "" }}
- onClick={this.props.showLinks}>
- <FontAwesomeIcon icon="arrow-left" 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> */}
- <FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} />
+ <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>
{this.showInfo ? <div className="linkEditor-moreInfo">
<div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div>
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index b0edd238e..98e4171f0 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -4,7 +4,7 @@
width: auto;
height: auto;
position: absolute;
- top : 0;
+ top: 0;
left: 0;
.linkMenu-list {
@@ -29,14 +29,17 @@
//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 {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 2d151e9bc..7b5fb0127 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -46,7 +46,6 @@ export class LinkMenu extends React.Component<Props> {
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;
}
}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 2f6b75437..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;
@@ -66,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}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index f70f5a23e..4e13ef8c8 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -67,6 +67,7 @@
color: rgb(60, 90, 156);
//display: inline;
text-overflow: break;
+ cursor: pointer;
}
}
}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index b451f0168..d1c839c3b 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, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -16,8 +16,8 @@ import { DocumentView } from '../nodes/DocumentView';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import { Tooltip } from '@material-ui/core';
-import { RichTextField } from '../../../fields/RichTextField';
import { DocumentType } from '../../documents/DocumentTypes';
+import { undoBatch } from '../../util/UndoManager';
library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
@@ -81,10 +81,7 @@ 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));
}
@@ -119,7 +116,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
document.addEventListener("pointerup", this.onLinkButtonUp);
if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
- console.log("outside click");
LinkDocPreview.LinkInfo = undefined;
}
}
@@ -153,27 +149,20 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
@action.bound
async followDefault() {
- console.log("FOLLOWWW");
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
- if (this.props.linkDoc.follow) {
- if (this.props.linkDoc.follow === "Default") {
- DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
- } else if (this.props.linkDoc.follow === "Always open in right tab") {
- this.props.addDocTab(this.props.destinationDoc, "onRight");
- } else if (this.props.linkDoc.follow === "Always open in new tab") {
- this.props.addDocTab(this.props.destinationDoc, "inTab");
- }
+ 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);
}
}
+ @undoBatch
@action
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
- //this.props.showLinks();
LinkDocPreview.LinkInfo = undefined;
DocumentLinksButton.EditLink = undefined;
}
@@ -189,7 +178,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
const eyeIcon = this.props.linkDoc.hidden ? "eye-slash" : "eye";
- let destinationIcon: string = "";;
+ let destinationIcon: FontAwesomeIconProps["icon"] = "question";
switch (this.props.destinationDoc.type) {
case DocumentType.IMG: destinationIcon = "image"; break;
case DocumentType.COMPARISON: destinationIcon = "columns"; break;
@@ -205,11 +194,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
case DocumentType.SCRIPTING: destinationIcon = "terminal"; break;
case DocumentType.IMPORT: destinationIcon = "cloud-upload-alt"; break;
case DocumentType.DOCHOLDER: destinationIcon = "expand"; break;
- default: "question";
+ 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, 19) + "..." : this.props.destinationDoc.title;
+ 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)
@@ -219,8 +210,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
StrCast(this.props.linkDoc.storedText).substr(0, 18)
: this.props.linkDoc.storedText : undefined : undefined;
- const showTitle = this.props.linkDoc.hidden ? "Show link" : "Hide link";
-
return (
<div className="linkMenu-item">
<div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
@@ -253,16 +242,16 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
{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> : <></>}
- <Tooltip title={showTitle}>
+ <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="Edit Link">
+ <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="Delete Link">
+ <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>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d79e2c9ff..ce39c3735 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -15,6 +15,7 @@ import { numberRange } from "../../../Utils";
import { ComputedField } from "../../../fields/ScriptField";
import { listSpec } from "../../../fields/Schema";
import { DocumentType } from "../../documents/DocumentTypes";
+import { InkingStroke } from "../InkingStroke";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -37,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
return min + rnd * (max - min);
}
get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
- get maskCentering() { return this.props.Document.isInkMask ? 2500 : 0; }
+ get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
@@ -157,8 +158,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
outline: this.Highlight ? "orange solid 2px" : "",
transform: this.transform,
transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
- width: this.props.Document.isInkMask ? 5000 : this.width,
- height: this.props.Document.isInkMask ? 5000 : this.height,
+ width: this.props.Document.isInkMask ? InkingStroke.MaskDim : this.width,
+ height: this.props.Document.isInkMask ? InkingStroke.MaskDim : this.height,
zIndex: this.ZInd,
mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
display: this.ZInd === -99 ? "none" : undefined,
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 0cf5505cc..0c4242172 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -180,7 +180,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
render() {
const containedDoc = Cast(this.dataDoc[this.fieldKey], Doc, null);
TraceMobx();
- return <div className="documentBox-container" ref={this._contRef}
+ return !containedDoc ? (null) : <div className="documentBox-container" ref={this._contRef}
onContextMenu={this.specificContextMenu}
onPointerDown={this.onPointerDown} onClick={this.onClick}
style={{
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f1438fd54..47dc0a773 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc";
+import { Doc, Opt, Field, AclPrivate } from "../../../fields/Doc";
import { Cast, StrCast, NumCast } from "../../../fields/Types";
import { OmitKeys, Without, emptyPath } from "../../../Utils";
import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox";
@@ -36,7 +36,7 @@ import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
import React = require("react");
import { RecommendationsBox } from "../RecommendationsBox";
-import { TraceMobx } from "../../../fields/util";
+import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
import { ScriptField } from "../../../fields/ScriptField";
import XRegExp = require("xregexp");
@@ -184,8 +184,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
const bindings = this.CreateBindings(onClick, onInput);
// layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-
- return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || this.layoutDoc[AclSym] === AclPrivate) ? (null) :
+ return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
<ObserverJsxParser
key={42}
blacklistedAttrs={[]}
@@ -201,7 +200,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
jsx={layoutFrame}
showWarnings={true}
- onError={(test: any) => { console.log(test); }}
+ onError={(test: any) => { console.log("DocumentContentsView:" + test); }}
/>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 35d99d44b..97e714cd5 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -13,18 +13,18 @@
color: black;
text-transform: uppercase;
letter-spacing: 2px;
- font-size: 75%;
+ font-size: 10px;
transition: transform 0.2s;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
- // &:hover {
- // background: deepskyblue;
- // transform: scale(1.05);
- // cursor: pointer;
- // }
+ &:hover {
+ // background: deepskyblue;
+ // transform: scale(1.05);
+ cursor: pointer;
+ }
}
.documentLinksButton {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 83710cfbf..c9d23ff3a 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,7 +10,7 @@ import React = require("react");
import { DocUtils } from "../../documents/Documents";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LinkDocPreview } from "./LinkDocPreview";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
import { LinkManager } from "../../util/LinkManager";
import { Tooltip } from "@material-ui/core";
@@ -68,8 +68,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
//action(() => Doc.BrushDoc(this.props.View.Document));
DocumentLinksButton.StartLink = this.props.View;
} 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];
}
@@ -93,28 +91,24 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
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 {
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;
- linkDoc ? linkDoc.hidden = true : null;
- linkDoc ? linkDoc.linkDisplay = true : null;
runInAction(() => {
if (linkDoc) {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 133;
- LinkCreatedBox.linkCreated = true;
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = e.screenX;
+ TaskCompletionBox.popupY = e.screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
LinkDescriptionPopup.popupX = e.screenX;
LinkDescriptionPopup.popupY = e.screenY - 100;
LinkDescriptionPopup.descriptionPopup = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
}
});
@@ -128,22 +122,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
finishLinkClick = (e: React.MouseEvent) => {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
- // action((e: React.PointerEvent<HTMLDivElement>) => {
- // Doc.UnBrushDoc(this.props.View.Document);
- // });
} else {
if (this.props.InMenu && !!!this.props.StartLink) {
if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks (). see note above in onLInkButtonMoved
+ runInAction(() => DocumentLinksButton.StartLink!._link = this.props.View._link = linkDoc);
+ setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
LinkManager.currentLink = linkDoc;
- linkDoc ? linkDoc.hidden = true : null;
- linkDoc ? linkDoc.linkDisplay = true : null;
runInAction(() => {
if (linkDoc) {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 133;
- LinkCreatedBox.linkCreated = true;
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = e.screenX;
+ TaskCompletionBox.popupY = e.screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
LinkDescriptionPopup.popupX = e.screenX;
@@ -151,7 +144,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
LinkDescriptionPopup.descriptionPopup = true;
}
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
}
});
}
@@ -168,8 +161,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const links = DocListCast(this.props.View.props.Document.links);
const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
-
- const title = this.props.InMenu ? menuTitle : "Tap to view links";
+ const buttonTitle = "Tap to view links";
+ const title = this.props.InMenu ? menuTitle : buttonTitle;
const startLink = <img
@@ -202,14 +195,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// 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="unlink" size="sm" /> : links.length} */}
- {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
- link : links.length} */}
+ {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
+ <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length}
- {this.props.InMenu ? this.props.StartLink ? startLink :
- endLink : 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" }}
@@ -217,10 +206,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
{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) :
- <Tooltip title={title}>
- {linkButton}
- </Tooltip>;
+ 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 11be4c2e7..7f86ec98e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,16 +3,15 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit, AclAdmin } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from '../../../fields/util';
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
+import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -42,9 +41,10 @@ import { RadialMenu } from './RadialMenu';
import React = require("react");
import { DocumentLinksButton } from './DocumentLinksButton';
import { MobileInterface } from '../../../mobile/MobileInterface';
-import { LinkCreatedBox } from './LinkCreatedBox';
+import { TaskCompletionBox } from './TaskCompletedBox';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { LinkManager } from '../../util/LinkManager';
+import CollectionMenu from '../collections/CollectionMenu';
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,
@@ -68,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;
@@ -127,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;
@@ -293,7 +295,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let stopPropagate = true;
let preventDefault = true;
!this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
- if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
if (!(e.nativeEvent as any).formattedHandled) {
if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
const func = () => this.onDoubleClickHandler.script.run({
@@ -587,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;
}
@@ -604,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";
}
@@ -619,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";
}
}
@@ -641,18 +629,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
const makeLink = action((linkDoc: Doc) => {
LinkManager.currentLink = linkDoc;
- linkDoc.hidden = true;
- linkDoc.linkDisplay = true;
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y - 33;
- LinkCreatedBox.linkCreated = true;
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = de.x;
+ TaskCompletionBox.popupY = de.y - 33;
+ TaskCompletionBox.taskCompleted = true;
LinkDescriptionPopup.popupX = de.x;
LinkDescriptionPopup.popupY = de.y;
LinkDescriptionPopup.descriptionPopup = true;
- setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500);
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
});
if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
@@ -713,7 +700,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+ setAcl = (acl: SharingPermissions) => {
this.dataDoc.ACL = this.props.Document.ACL = acl;
DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
@@ -721,9 +708,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
});
}
+
@undoBatch
@action
- testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+ testAcl = (acl: SharingPermissions) => {
this.dataDoc.author = this.props.Document.author = "ADMIN";
this.dataDoc.ACL = this.props.Document.ACL = acl;
DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
@@ -767,11 +755,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.contextMenuItems?.().forEach(item =>
cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+ const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+ const appearance = cm.findByDescription("UI Controls...");
+ const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
+ templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
+ //DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
+ !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
+
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- 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" });
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
@@ -784,7 +777,7 @@ 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...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
if (this.layoutDoc.onDragStart) {
@@ -796,6 +789,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const more = cm.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
+ moreItems.push({
+ description: "Download document", icon: "download", event: async () => {
+ Doc.Zip(this.props.Document);
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
+ }
+ });
if (!Doc.UserDoc().noviceMode) {
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -805,42 +808,33 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
- moreItems.push({
- description: "Download document", icon: "download", event: async () => {
- const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
- qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
- });
- console.log(response ? JSON.parse(response) : undefined);
- }
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- });
moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
}
- moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- //!Doc.UserDoc().novice && helpItems.push({ description: "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" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+ helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
// const existingAcls = cm.findByDescription("Privacy...");
// const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
- // aclItems.push({ description: "Make Add Only", event: () => this.setAcl("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" });
+ // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
// !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+ // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
+
// const recommender_subitems: ContextMenuProps[] = [];
// recommender_subitems.push({
@@ -921,7 +915,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
const documents: Doc[] = [];
const allDocs = await SearchUtil.GetAllDocs();
- // allDocs.forEach(doc => console.log(doc.title));
// clears internal representation of documents as vectors
ClientRecommender.Instance.reset_docs();
//ClientRecommender.Instance.arxivrequest("electrons");
@@ -1070,11 +1063,12 @@ 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.hideAllLinks ? (null) : this.allAnchors}
{/* {this.allAnchors} */}
- {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
+ {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) :
+ <DocumentLinksButton View={this} Offset={[-15, 0]} />}
</div>
);
}
@@ -1103,7 +1097,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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
- DocUtils.FilterDocs(DocListCast(this.Document.links), this.props.docFilters(), []).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}
@@ -1141,7 +1135,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) :
@@ -1198,7 +1192,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
render() {
if (!(this.props.Document instanceof Doc)) return (null);
- if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
+ if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null);
if (this.props.Document.hidden) return (null);
const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
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 fe0f067ad..5b85d8b0b 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -11,7 +11,6 @@
.fontIconBox-label {
background: gray;
color:white;
- margin-left: -10px;
border-radius: 8px;
width:100%;
position: absolute;
@@ -19,6 +18,8 @@
font-size: 8px;
margin-top:4px;
letter-spacing: normal;
+ left: 0;
+ overflow: hidden;
}
svg {
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 86e9a4527..2611d2ca7 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -11,6 +11,7 @@ import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
import { Doc } from '../../../fields/Doc';
import { ContextMenu } from '../ContextMenu';
import { ScriptField } from '../../../fields/ScriptField';
+import { Tooltip } from '@material-ui/core';
const FontIconSchema = createSchema({
icon: "string"
});
@@ -60,14 +61,18 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
render() {
const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc);
const refLayout = Doc.Layout(referenceDoc);
- return <button className="fontIconBox-outerDiv" title={StrCast(this.layoutDoc.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
+ const button = <button className="fontIconBox-outerDiv" ref={this._ref} onContextMenu={this.specificContextMenu}
style={{
padding: Cast(this.layoutDoc._xPadding, "number", null),
background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
}}>
- <FontAwesomeIcon className="fontIconBox-icon" icon={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, 6)} </div>}
+ <FontAwesomeIcon className="fontIconBox-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
+ {!this.rootDoc.title ? (null) : <div className="fontIconBox-label" style={{ width: this.rootDoc.label ? "max-content" : undefined }}> {StrCast(this.rootDoc.label, StrCast(this.rootDoc.title).substring(0, 6))} </div>}
</button>;
+ return !this.layoutDoc.toolTip ? button :
+ <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+ {button}
+ </Tooltip>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4eba21eab..5f689624c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -158,6 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (field) {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
+ funcs.push({ description: "Make Background", event: () => this.layoutDoc.isBackground = true, 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" });
@@ -315,7 +316,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
considerGooglePhotosLink = () => {
const remoteUrl = this.dataDoc.googlePhotosUrl;
- return !remoteUrl ? (null) : (<img
+ return !remoteUrl ? (null) : (<img draggable={false}
style={{ transform: `scale(${this.props.ContentScaling()})`, transformOrigin: "bottom right" }}
id={"google-photos"}
src={"/assets/google_photos.png"}
@@ -340,7 +341,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
}
return (
<img
- id={"upload-icon"}
+ id={"upload-icon"} draggable={false}
style={{ transform: `scale(${1 / this.props.ContentScaling()})`, transformOrigin: "bottom right" }}
src={`/assets/${this.uploadIcon}`}
onClick={async () => {
@@ -415,7 +416,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
<div className="imageBox-fader" >
<img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
src={srcpath}
- style={{ transform, transformOrigin }}
+ style={{ transform, transformOrigin }} draggable={false}
width={nativeWidth}
ref={this._imgRef}
onError={this.onError} />
@@ -423,7 +424,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
<img className="imageBox-fadeaway"
key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
src={fadepath}
- style={{ transform, transformOrigin }}
+ style={{ transform, transformOrigin }} draggable={false}
width={nativeWidth}
ref={this._imgRef}
onError={this.onError} /></div>}
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
deleted file mode 100644
index 648ae23c8..000000000
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
-import "./LinkCreatedBox.scss";
-import { observable, action } from "mobx";
-import { Fade } from "@material-ui/core";
-
-
-@observer
-export class LinkCreatedBox extends React.Component<{}> {
-
- @observable public static linkCreated: boolean = false;
- @observable public static popupX: number = 500;
- @observable public static popupY: number = 150;
-
- @action
- public static changeLinkCreated = () => {
- LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated;
- }
-
- render() {
- return <Fade in={LinkCreatedBox.linkCreated}>
- <div className="linkCreatedBox-fade"
- style={{
- left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
- top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
- }}>Link Created</div>
- </Fade>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 06e8d30d1..d8fe47f4e 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -4,7 +4,7 @@ import "./LinkDescriptionPopup.scss";
import { observable, action } from "mobx";
import { EditableView } from "../EditableView";
import { LinkManager } from "../../util/LinkManager";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
@observer
@@ -31,7 +31,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
onClick = (e: PointerEvent) => {
if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
LinkDescriptionPopup.descriptionPopup = false;
- LinkCreatedBox.linkCreated = false;
+ TaskCompletionBox.taskCompleted = false;
}
}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 079920f56..ebb916307 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { LinkManager } from '../../util/LinkManager';
import { DocumentLinksButton } from './DocumentLinksButton';
import { ContextMenu } from '../ContextMenu';
+import { undoBatch } from '../../util/UndoManager';
interface Props {
linkDoc?: Doc;
@@ -32,14 +33,6 @@ export class LinkDocPreview extends React.Component<Props> {
_editRef = React.createRef<HTMLDivElement>();
@action
- deleteLink = (): void => {
- this.props.linkDoc ? LinkManager.Instance.deleteLink(this.props.linkDoc) : null;
- //this.props.showLinks();
- LinkDocPreview.LinkInfo = undefined;
- DocumentLinksButton.EditLink = undefined;
- }
-
- @action
onContextMenu = (e: React.MouseEvent) => {
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
@@ -123,7 +116,8 @@ export class LinkDocPreview extends React.Component<Props> {
bringToFront={returnFalse}
ContentScaling={returnOne}
NativeWidth={returnZero}
- NativeHeight={returnZero} />;
+ NativeHeight={returnZero}
+ backgroundColor={this.props.backgroundColor} />;
}
render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1c5825a8f..323da1233 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -268,7 +268,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
if (!this._pdfjsRequested) {
this._pdfjsRequested = true;
const promise = Pdfjs.getDocument(pdfUrl.url.href).promise;
- promise.then(action(pdf => { this._pdf = pdf; console.log("promise"); }));
+ promise.then(action(pdf => this._pdf = pdf));
}
}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 8818d375e..a304ced18 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -297,7 +297,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
(this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
render() {
- // console.log("render = " + this.layoutDoc.title + " " + this.layoutDoc.presStatus);
// const presOrderedDocs = DocListCast(this.rootDoc.presOrderedDocs);
// if (presOrderedDocs.length != this.childDocs.length || presOrderedDocs.some((pd, i) => pd !== this.childDocs[i])) {
// this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.slice());
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index a3ac09a11..7f0956e51 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -89,7 +89,6 @@ export class RadialMenu extends React.Component {
@action
componentDidMount = () => {
- console.log(this._pageX);
document.addEventListener("pointerdown", this.onPointerDown);
document.addEventListener("pointerup", this.onPointerUp);
this.previewcircle();
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index f7dee0896..1cd29d795 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -112,7 +112,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
return returnedUri;
} catch (e) {
- console.log(e);
+ console.log("ScreenShotBox:" + e);
}
}
@observable _screenCapture = false;
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 04ac34cc2..bc43cd473 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -109,7 +109,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, Sc
@action
resetSuggestionPos(caret: any) {
if (!this._suggestionRef.current || !this._scriptTextRef.current) return;
- console.log('(top, left, height) = (%s, %s, %s)', caret.top, caret.left, caret.height);
const suggestionWidth = this._suggestionRef.current.offsetWidth;
const scriptWidth = this._scriptTextRef.current.offsetWidth;
const top = caret.top;
diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/TaskCompletedBox.scss
index 3cbd38b55..80b750b39 100644
--- a/src/client/views/nodes/LinkCreatedBox.scss
+++ b/src/client/views/nodes/TaskCompletedBox.scss
@@ -1,7 +1,6 @@
-.linkCreatedBox-fade {
+.taskCompletedBox-fade {
border: 1px solid rgb(100, 100, 100);
-
width: auto;
position: absolute;
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
new file mode 100644
index 000000000..89602f219
--- /dev/null
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -0,0 +1,32 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import "./TaskCompletedBox.scss";
+import { observable, action } from "mobx";
+import { Fade } from "@material-ui/core";
+
+
+@observer
+export class TaskCompletionBox extends React.Component<{}> {
+
+ @observable public static taskCompleted: boolean = false;
+ @observable public static popupX: number = 500;
+ @observable public static popupY: number = 150;
+ @observable public static textDisplayed: string;
+
+ @action
+ public static toggleTaskCompleted = () => {
+ TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted;
+ }
+
+ render() {
+ return <Fade in={TaskCompletionBox.taskCompleted}>
+ <div className="taskCompletedBox-fade"
+ style={{
+ left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500,
+ top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150,
+ }}>{TaskCompletionBox.textDisplayed}</div>
+ </Fade>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a5c6c4a48..ee92e517c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -205,7 +205,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
return returnedUri;
} catch (e) {
- console.log(e);
+ console.log("VideoBox :" + e);
}
}
@observable _screenCapture = false;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 4623444b9..875142169 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,135 +1,155 @@
@import "../globalCssVariables.scss";
-.webBox-container, .webBox-container-dragging {
- transform-origin: top left;
- width: 100%;
- height: 100%;
+.webBox {
+ height:100%;
+ position: relative;
+ display: flex;
- .webBox-htmlSpan {
+ .pdfViewerDash-dragAnnotationBox {
+ position:absolute;
+ background-color: transparent;
+ opacity: 0.1;
+ }
+ .webBox-annotationLayer {
position: absolute;
+ transform-origin: left top;
top: 0;
- left: 0;
- }
- .webBox-cont {
+ width: 100%;
pointer-events: none;
+ mix-blend-mode: multiply; // bcz: makes text fuzzy!
}
- .webBox-cont, .webBox-cont-interactive {
- padding: 0vw;
+ .webBox-annotationBox {
position: absolute;
- top: 0;
- left: 0;
+ background-color: rgba(245, 230, 95, 0.616);
+ }
+ .webBox-container {
+ transform-origin: top left;
width: 100%;
height: 100%;
- transform-origin: top left;
- overflow: auto;
- .webBox-iframe {
+
+ .webBox-htmlSpan {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ .webBox-cont {
+ pointer-events: none;
+ }
+ .webBox-cont, .webBox-cont-interactive {
+ padding: 0vw;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform-origin: top left;
+ overflow: auto;
+ .webBox-iframe {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top:0;
+ }
+ }
+ .webBox-cont-interactive {
+ span {
+ user-select: text !important;
+ }
+ }
+ .webBox-outerContent {
width: 100%;
height: 100%;
position: absolute;
- top:0;
+ top: 0;
+ left: 0;
+ overflow: auto;
}
- }
- .webBox-cont-interactive {
- span {
- user-select: text !important;
+ div.webBox-outerContent::-webkit-scrollbar-thumb {
+ display:none;
}
}
- .webBox-outerContent {
+
+
+ .webBox-overlay {
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
- overflow: auto;
- .webBox-innerContent {
- width:100%;
- }
- }
- div.webBox-outerContent::-webkit-scrollbar-thumb {
- display:none;
}
-}
-
-
-.webBox-overlay {
- width: 100%;
- height: 100%;
- position: absolute;
-}
-.webBox-buttons {
- margin-left: 44;
- background:lightGray;
- width: 100%;
-}
-.webBox-freeze {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 5px;
- width: 30px;
-}
-
-.webBox-urlEditor {
- position: relative;
- opacity: 0.9;
- z-index: 9001;
- transition: top .5s;
-
- .urlEditor {
- display: grid;
- grid-template-columns: 1fr auto;
- padding-bottom: 10px;
- overflow: hidden;
-
- .editorBase {
- display: flex;
-
- .editor-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 40px;
- transform-origin: top left;
- }
+ .webBox-buttons {
+ margin-left: 44;
+ background:lightGray;
+ width: 100%;
+ }
+ .webBox-freeze {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 5px;
+ width: 30px;
+ }
- .switchToText {
- color: $main-accent;
+ .webBox-urlEditor {
+ position: relative;
+ opacity: 0.9;
+ z-index: 9001;
+ transition: top .5s;
+
+ .urlEditor {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ padding-bottom: 10px;
+ overflow: hidden;
+
+ .editorBase {
+ display: flex;
+
+ .editor-collapse {
+ transition: all .5s, opacity 0.3s;
+ position: absolute;
+ width: 40px;
+ transform-origin: top left;
+ }
+
+ .switchToText {
+ color: $main-accent;
+ }
+
+ .switchToText:hover {
+ color: $dark-color;
+ }
}
- .switchToText:hover {
- color: $dark-color;
+ button:hover {
+ transform: scale(1);
}
}
+ }
- button:hover {
- transform: scale(1);
- }
+ .webpage-urlInput {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ letter-spacing: 2px;
+ outline-color: black;
+ background: rgb(238, 238, 238);
+ width: 100%;
+ margin-right: 10px;
+ height: 100%;
}
-}
-
-.webpage-urlInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: grey;
- letter-spacing: 2px;
- outline-color: black;
- background: rgb(238, 238, 238);
- width: 100%;
- margin-right: 10px;
- height: 100%;
-}
-
-.touch-iframe-overlay {
- width: 100%;
- height: 100%;
- position: absolute;
-
- .indicator {
+
+ .touch-iframe-overlay {
+ width: 100%;
+ height: 100%;
position: absolute;
- &.active {
- background-color: rgba(0, 0, 0, 0.1);
+ .indicator {
+ position: absolute;
+
+ &.active {
+ background-color: rgba(0, 0, 0, 0.1);
+ }
}
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 05355caba..7e3e662a4 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,53 +1,70 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, observable, trace, IReactionDisposer, reaction, runInAction } from "mobx";
+import { faMousePointer, faPen, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, FieldResult, DocListCast } from "../../../fields/Doc";
+import { Dictionary } from "typescript-collections";
+import * as WebRequest from 'web-request';
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
import { HtmlField } from "../../../fields/HtmlField";
import { InkTool } from "../../../fields/InkField";
-import { makeInterface, listSpec } from "../../../fields/Schema";
-import { Cast, NumCast, BoolCast, StrCast } from "../../../fields/Types";
+import { List } from "../../../fields/List";
+import { listSpec, makeInterface } from "../../../fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
-import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
+import { TraceMobx } from "../../../fields/util";
+import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
+import { undoBatch } from "../../util/UndoManager";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import Annotation from "../pdf/Annotation";
+import PDFMenu from "../pdf/PDFMenu";
+import { PdfViewerMarquee } from "../pdf/PDFViewer";
import { FieldView, FieldViewProps } from './FieldView';
import "./WebBox.scss";
+import "../pdf/PDFViewer.scss";
import React = require("react");
-import * as WebRequest from 'web-request';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { undoBatch } from "../../util/UndoManager";
-import { List } from "../../../fields/List";
const htmlToText = require("html-to-text");
-library.add(faStickyNote);
-
type WebDocument = makeInterface<[typeof documentSchema]>;
const WebDocument = makeInterface(documentSchema);
@observer
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ static _annotationStyle: any = addStyleSheet();
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _startX: number = 0;
+ private _startY: number = 0;
+ @observable private _marqueeX: number = 0;
+ @observable private _marqueeY: number = 0;
+ @observable private _marqueeWidth: number = 0;
+ @observable private _marqueeHeight: number = 0;
+ @observable private _marqueeing: boolean = false;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
@observable private _url: string = "hello";
@observable private _pressX: number = 0;
@observable private _pressY: number = 0;
+ @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ private _selectionReactionDisposer?: IReactionDisposer;
+ private _scrollReactionDisposer?: IReactionDisposer;
+ private _moveReactionDisposer?: IReactionDisposer;
private _keyInput = React.createRef<HTMLInputElement>();
private _longPressSecondsHack?: NodeJS.Timeout;
private _outerRef = React.createRef<HTMLDivElement>();
private _iframeRef = React.createRef<HTMLIFrameElement>();
private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
private _iframeDragRef = React.createRef<HTMLDivElement>();
- private _reactionDisposer?: IReactionDisposer;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
iframeLoaded = action((e: any) => {
@@ -60,21 +77,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc._scrollTop);
iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft);
}
- this._reactionDisposer?.();
- this._reactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
- ({ x, y }) => {
- if (y !== undefined) {
- this._outerRef.current!.scrollTop = y;
- this.layoutDoc._scrollY = undefined;
- }
- if (x !== undefined) {
- this._outerRef.current!.scrollLeft = x;
- this.layoutDoc.scrollX = undefined;
- }
- },
+ this._scrollReactionDisposer?.();
+ this._scrollReactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
+ ({ x, y }) => this.updateScroll(x, y),
{ fireImmediately: true }
);
});
+
+ updateScroll = (x: Opt<number>, y: Opt<number>) => {
+ if (y !== undefined) {
+ this._outerRef.current!.scrollTop = y;
+ this.layoutDoc._scrollY = undefined;
+ }
+ if (x !== undefined) {
+ this._outerRef.current!.scrollLeft = x;
+ this.layoutDoc.scrollX = undefined;
+ }
+ }
+
setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
iframedown = (e: PointerEvent) => {
this._setPreviewCursor?.(e.screenX, e.screenY, false);
@@ -89,6 +109,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
runInAction(() => this._url = urlField?.url.toString() || "");
+ this._moveReactionDisposer = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
+ () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
+
+ this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
+ selected => {
+ if (!selected) {
+ this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
+ PDFMenu.Instance.fadeOut(true);
+ }
+ },
+ { fireImmediately: true });
+
document.addEventListener("pointerup", this.onLongPressUp);
document.addEventListener("pointermove", this.onLongPressMove);
const field = Cast(this.rootDoc[this.props.fieldKey], WebField);
@@ -112,7 +145,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
componentWillUnmount() {
- this._reactionDisposer?.();
+ this._moveReactionDisposer?.();
+ this._selectionReactionDisposer?.();
+ this._scrollReactionDisposer?.();
document.removeEventListener("pointerup", this.onLongPressUp);
document.removeEventListener("pointermove", this.onLongPressMove);
this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown);
@@ -189,7 +224,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.dataDoc[this.fieldKey] = new WebField(URLy);
this.dataDoc[this.annotationKey] = new List<Doc>([]);
} catch (e) {
- console.log("Error in URL :" + this._url);
+ console.log("WebBox URL error:" + this._url);
}
}
@@ -267,11 +302,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
);
}
+
@action
toggleCollapse = () => {
this._collapsed = !this._collapsed;
}
+
+
_ignore = 0;
onPreWheel = (e: React.WheelEvent) => {
this._ignore = e.timeStamp;
@@ -284,6 +322,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
e.stopPropagation();
}
}
+
onPostWheel = (e: React.WheelEvent) => {
if (this._ignore !== e.timeStamp) {
e.stopPropagation();
@@ -399,7 +438,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ funcs.push({ description: (this.layoutDoc.UseCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.UseCors = !this.layoutDoc.UseCors, icon: "snowflake" });
cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
@@ -430,6 +469,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
return (<>
<div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !decInteracting ? "-interactive" : "")}
+ style={{ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%" }}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{view}
</div>;
@@ -444,60 +484,250 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
{this.urlEditor()}
</>);
}
+
+
+
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
+ @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
+
+ @undoBatch
+ @action
+ makeAnnotationDocument = (color: string): Opt<Doc> => {
+ if (this._savedAnnotations.size() === 0) return undefined;
+ const anno = this._savedAnnotations.values()[0][0];
+ const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.Document, title: "Annotation on " + this.Document.title });
+ if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
+ if (anno.style.top) annoDoc.y = NumCast(this.layoutDoc._scrollTop) + parseInt(anno.style.top);
+ if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+ if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
+ anno.remove();
+ this._savedAnnotations.clear();
+ return annoDoc;
+ }
+ @computed get annotationLayer() {
+ TraceMobx();
+ return <div className="webBox-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight) }} ref={this._annotationLayer}>
+ {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+ <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+ }
+ </div>;
+ }
+ @action
+ createAnnotation = (div: HTMLDivElement, page: number) => {
+ if (this._annotationLayer.current) {
+ if (div.style.top) {
+ div.style.top = (parseInt(div.style.top)).toString();
+ }
+ this._annotationLayer.current.append(div);
+ div.style.backgroundColor = "#ACCEF7";
+ div.style.opacity = "0.5";
+ const savedPage = this._savedAnnotations.getValue(page);
+ if (savedPage) {
+ savedPage.push(div);
+ this._savedAnnotations.setValue(page, savedPage);
+ }
+ else {
+ this._savedAnnotations.setValue(page, [div]);
+ }
+ }
+ }
+
+ @action
+ highlight = (color: string) => {
+ // creates annotation documents for current highlights
+ const annotationDoc = this.makeAnnotationDocument(color);
+ annotationDoc && Doc.AddDocToList(this.props.Document, this.annotationKey, annotationDoc);
+ return annotationDoc;
+ }
+ /**
+ * This is temporary for creating annotations from highlights. It will
+ * start a drag event and create or put the necessary info into the drag event.
+ */
+ @action
+ startDrag = async (e: PointerEvent, ele: HTMLElement) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const clipDoc = Doc.MakeAlias(this.dataDoc);
+ clipDoc._fitWidth = true;
+ clipDoc._width = this.marqueeWidth();
+ clipDoc._height = this.marqueeHeight();
+ clipDoc._scrollTop = this.marqueeY();
+ const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
+ Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
+ clipDoc.rootDocument = targetDoc;
+ targetDoc.layoutKey = "layout";
+ const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
+ if (annotationDoc) {
+ DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
+ dragComplete: e => {
+ if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
+ DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+ annotationDoc.isLinkButton = true;
+ e.annoDragData.dropDocument.isPushpin = true;
+ e.annoDragData.dropDocument.isLinkButton = true;
+ }
+ }
+ });
+ }
+ }
+ @action
+ onMarqueeDown = (e: React.PointerEvent) => {
+ this._marqueeing = false;
+ if (!e.altKey && e.button === 0 && this.active(true)) {
+ // clear out old marquees and initialize menu for new selection
+ PDFMenu.Instance.StartDrag = this.startDrag;
+ PDFMenu.Instance.Highlight = this.highlight;
+ PDFMenu.Instance.Status = "pdf";
+ PDFMenu.Instance.fadeOut(true);
+ this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
+ if ((e.target as any)?.parentElement.className === "textLayer") {
+ // start selecting text if mouse down on textLayer spans
+ }
+ else if (this._mainCont.current) {
+ // set marquee x and y positions to the spatially transformed position
+ const boundingRect = this._mainCont.current.getBoundingClientRect();
+ this._startX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
+ this._startY = (e.clientY - boundingRect.top) / boundingRect.height * (this.Document._nativeHeight || 1);
+ this._marqueeHeight = this._marqueeWidth = 0;
+ this._marqueeing = true;
+ }
+ document.removeEventListener("pointermove", this.onSelectMove);
+ document.addEventListener("pointermove", this.onSelectMove);
+ document.removeEventListener("pointerup", this.onSelectEnd);
+ document.addEventListener("pointerup", this.onSelectEnd);
+ }
+ }
+ @action
+ onSelectMove = (e: PointerEvent): void => {
+ if (this._marqueeing && this._mainCont.current) {
+ // transform positions and find the width and height to set the marquee to
+ const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const curX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
+ const curY = (e.clientY - boundingRect.top) / boundingRect.height * (this.Document._nativeHeight || 1);
+ this._marqueeWidth = curX - this._startX;
+ this._marqueeHeight = curY - this._startY;
+ this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
+ this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
+ this._marqueeWidth = Math.abs(this._marqueeWidth);
+ this._marqueeHeight = Math.abs(this._marqueeHeight);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else if (e.target && (e.target as any).parentElement === this._mainCont.current) {
+ e.stopPropagation();
+ }
+ }
+
+ @action
+ onSelectEnd = (e: PointerEvent): void => {
+ clearStyleSheetRules(WebBox._annotationStyle);
+ this._savedAnnotations.clear();
+ if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
+ const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
+ if (marquees?.length) { // copy the marquee and convert it to a permanent annotation.
+ const style = (marquees[0] as HTMLDivElement).style;
+ const copy = document.createElement("div");
+ copy.style.left = style.left;
+ copy.style.top = style.top;
+ copy.style.width = style.width;
+ copy.style.height = style.height;
+ copy.style.border = style.border;
+ copy.style.opacity = style.opacity;
+ (copy as any).marqueeing = true;
+ copy.className = "webBox-annotationBox";
+ this.createAnnotation(copy, 0);
+ }
+
+ if (!e.ctrlKey) {
+ PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight };
+ }
+ PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+ }
+ //this._marqueeing = false;
+
+ if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
+ this.highlight("rgba(245, 230, 95, 0.616)"); // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
+ }
+ else {
+ PDFMenu.Instance.StartDrag = this.startDrag;
+ PDFMenu.Instance.Highlight = this.highlight;
+ }
+ document.removeEventListener("pointermove", this.onSelectMove);
+ document.removeEventListener("pointerup", this.onSelectEnd);
+ }
+ marqueeWidth = () => this._marqueeWidth;
+ marqueeHeight = () => this._marqueeHeight;
+ marqueeX = () => this._marqueeX;
+ marqueeY = () => this._marqueeY;
+ marqueeing = () => this._marqueeing;
scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
render() {
- return (<div className={`webBox-container`}
- style={{
- transform: `scale(${this.props.ContentScaling()})`,
- width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
- height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
- pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
- }}
- onContextMenu={this.specificContextMenu}>
- <base target="_blank" />
- {this.content}
- <div className={"webBox-outerContent"} ref={this._outerRef}
- style={{ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none" }}
- onWheel={e => e.stopPropagation()}
- onScroll={e => {
- const iframe = this._iframeRef?.current?.contentDocument;
- const outerFrame = this._outerRef.current;
- if (iframe && outerFrame) {
- if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
- iframe.children[0].scrollTop = outerFrame.scrollTop;
- }
- if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) {
- iframe.children[0].scrollLeft = outerFrame.scrollLeft;
+ return (<div className="webBox" ref={this._mainCont} >
+ <div className={`webBox-container`}
+ style={{
+ position: undefined,
+ transform: `scale(${this.props.ContentScaling()})`,
+ width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
+ height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
+ pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+ }}
+ onContextMenu={this.specificContextMenu}>
+ <base target="_blank" />
+ {this.content}
+ <div className={"webBox-outerContent"} ref={this._outerRef}
+ style={{
+ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%",
+ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none"
+ }}
+ onWheel={e => e.stopPropagation()}
+ onPointerDown={this.onMarqueeDown}
+ onScroll={e => {
+ const iframe = this._iframeRef?.current?.contentDocument;
+ const outerFrame = this._outerRef.current;
+ if (iframe && outerFrame) {
+ if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
+ iframe.children[0].scrollTop = outerFrame.scrollTop;
+ }
+ if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) {
+ iframe.children[0].scrollLeft = outerFrame.scrollLeft;
+ }
}
- }
- //this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
- }}>
- <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight), width: 4000 }}>
- <CollectionFreeFormView {...this.props}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationKey}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- focus={this.props.focus}
- setPreviewCursor={this.setPreviewCursor}
- isSelected={this.props.isSelected}
- isAnnotationOverlay={true}
- select={emptyFunction}
- active={this.active}
- ContentScaling={returnOne}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.scrollXf}
- renderDepth={this.props.renderDepth + 1}
- docFilters={this.props.docFilters}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
- </CollectionFreeFormView>
+ //this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
+ }}>
+ <div className={"webBox-innerContent"} style={{
+ height: NumCast(this.layoutDoc.scrollHeight),
+ pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+ }}>
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ annotationsKey={this.annotationKey}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ focus={this.props.focus}
+ setPreviewCursor={this.setPreviewCursor}
+ isSelected={this.props.isSelected}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ active={this.active}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.scrollXf}
+ renderDepth={this.props.renderDepth + 1}
+ docFilters={this.props.docFilters}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+ </CollectionFreeFormView>
+ </div>
</div>
- </div>
- </div >);
+ {this.annotationLayer}
+ <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
+ </div >
+ </div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 5c3f3dcc9..212da3f3d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -209,7 +209,7 @@ export class DashDocView extends React.Component<IDashDocView> {
try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
} catch (e) {
- console.log(e);
+ console.log("DashDocView:" + e);
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f87b28c7f..6b6fc5da2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import applyDevTools = require("prosemirror-dev-tools");
import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../../fields/Schema";
import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../../DocServer";
@@ -232,32 +232,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
- if (!this.dataDoc[AclSym]) {
+ let unchanged = true;
+ const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
this._applyingChange = true;
(curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if (json !== curLayout?.Data) {
+ if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
!curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
!curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
+ unchanged = false;
}
} else { // if we've deleted all the text in a note driven by a template, then restore the template data
this.dataDoc[this.props.fieldKey] = undefined;
this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ unchanged = false;
}
this._applyingChange = false;
+ if (!unchanged) {
+ this.updateTitle();
+ this.tryUpdateHeight();
+ }
}
} else {
const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!);
json.selection = state.toJSON().selection;
this._editorView.updateState(EditorState.fromJSON(this.config, json));
}
- this.updateTitle();
- this.tryUpdateHeight();
}
}
@@ -420,16 +426,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
}
if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
}
if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
}
if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
}
if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
}
if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
@@ -465,9 +471,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- const appearance = ContextMenu.Instance.findByDescription("Appearance...");
- const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
-
const changeItems: ContextMenuProps[] = [];
const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
DocListCast(noteTypesDoc?.data).forEach(note => {
@@ -479,24 +482,6 @@ 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...", 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" });
- uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
- !Doc.UserDoc().noviceMode && uicontrols.push({
- description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
- proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
- });
-
- 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" });
-
- !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({
@@ -510,8 +495,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.updateHighlights();
}, icon: "expand-arrows-alt"
}));
- funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
- funcs.push({
+
+
+ const uicontrols: ContextMenuProps[] = [];
+ uicontrols.push({ description: `${this.layoutDoc._showSidebar ? "Hide" : "Show"} Sidebar`, event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
+ uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
+ uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
+ !Doc.UserDoc().noviceMode && uicontrols.push({
+ description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
+ proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
+ });
+ cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+
+ const appearance = cm.findByDescription("Appearance...");
+ const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
+ appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
+ 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({
description: "Convert to be a template style", event: () => {
if (!this.layoutDoc.isTemplateDoc) {
const title = StrCast(this.rootDoc.title);
@@ -536,11 +537,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
}, icon: "eye"
});
- funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-
- funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
- 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" });
+ cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+
+ const options = cm.findByDescription("Options...");
+ const optionItems = options && "subitems" in options ? options.subitems : [];
+ !Doc.UserDoc().noviceMode && optionItems.push({ description: this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
+ optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+ optionItems.push({ description: `${!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" });
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
this._downX = this._downY = Number.NaN;
}
@@ -557,11 +561,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
- @action
- toggleMenubar = () => {
- this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled";
- }
-
recordBullet = async () => {
const completedCue = "end session";
const results = await DictationManager.Controls.listen({
@@ -691,7 +690,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
);
this._disposers.editorState = reaction(
() => {
- if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
+ if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
}
return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
@@ -1036,7 +1035,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView?.destroy();
}
- static _downEvent: any;
+ _downEvent: any;
_downX = 0;
_downY = 0;
_break = false;
@@ -1056,7 +1055,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._downX = e.clientX;
this._downY = e.clientY;
this.doLinkOnDeselect();
- FormattedTextBox._downEvent = true;
+ this._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
e.preventDefault();
@@ -1072,8 +1071,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onPointerUp = (e: React.PointerEvent): void => {
- if (!FormattedTextBox._downEvent) return;
- FormattedTextBox._downEvent = false;
+ if (!this._downEvent) return;
+ this._downEvent = false;
if (!(e.nativeEvent as any).formattedHandled) {
const editor = this._editorView!;
FormattedTextBoxComment.textBox = this;
@@ -1092,7 +1091,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onDoubleClick = (e: React.MouseEvent): void => {
this.doLinkOnDeselect();
- FormattedTextBox._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
e.preventDefault();
@@ -1244,7 +1242,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
richTextMenuPlugin() {
return new Plugin({
view(newView) {
- RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
+ RichTextMenu.Instance?.changeView(newView);
return RichTextMenu.Instance;
}
});
@@ -1299,9 +1297,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (e.key === "Tab" || e.key === "Enter") {
e.preventDefault();
}
- const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
- this._lastTimedMark = mark;
- // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+ if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) {
+ const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+ }
if (!this._undoTyping) {
this.startUndoTypingBatch();
@@ -1402,12 +1401,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onDoubleClick={this.onDoubleClick}
>
<div className={`formattedTextBox-outer`} ref={this._scrollRef}
- style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
+ style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.active() ? "none" : undefined }}
onScroll={this.onscrolled} onDrop={this.ondrop} >
<div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
style={{
padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
- pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
+ pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
}}
/>
</div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index fa2548cb5..6f3984f39 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -22,6 +22,7 @@ import { LinkManager } from "../../../util/LinkManager";
import { LinkDocPreview } from "../LinkDocPreview";
import { DocumentLinksButton } from "../DocumentLinksButton";
import { Tooltip } from "@material-ui/core";
+import { undoBatch } from "../../../util/UndoManager";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -143,6 +144,7 @@ export class FormattedTextBoxComment {
}
}
+ @undoBatch
@action
deleteLink = () => {
FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
@@ -270,14 +272,14 @@ export class FormattedTextBoxComment {
</div>
<div className="wrapper" style={{ float: "right" }}>
- <Tooltip title="Delete Link" placement="top">
+ <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="Follow Link" placement="top">
+ <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"
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 172cde3e0..8faf752b4 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -103,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(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 });
+ const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _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 8da1f99b5..47a4911b8 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -23,7 +23,8 @@ import { updateBullets } from "./ProsemirrorExampleTransfer";
import "./RichTextMenu.scss";
import { schema } from "./schema_rts";
import { TraceMobx } from "../../../../fields/util";
-import { UndoManager } from "../../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../../util/UndoManager";
+import { Tooltip } from "@material-ui/core";
const { toggleMark } = require("prosemirror-commands");
library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
@@ -155,7 +156,9 @@ export default class RichTextMenu extends AntimodeMenu {
@action
changeView(view: EditorView) {
- this.view = view;
+ if ((view as any)?.TextView?.props.isSelected(true)) {
+ this.view = view;
+ }
}
update(view: EditorView, lastState: EditorState | undefined) {
@@ -164,8 +167,7 @@ export default class RichTextMenu extends AntimodeMenu {
@action
public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
- if (!view) {
- console.log("no editor? why?");
+ if (!view || !(view as any).TextView?.props.isSelected(true)) {
return;
}
this.view = view;
@@ -218,7 +220,7 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveAlignment() {
- if (this.view && this.TextView.props.isSelected()) {
+ if (this.view && this.TextView.props.isSelected(true)) {
const path = (this.view.state.selection.$from as any).path;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
@@ -231,7 +233,7 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveListStyle() {
- if (this.view && this.TextView.props.isSelected()) {
+ 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) {
@@ -251,7 +253,7 @@ export default class RichTextMenu extends AntimodeMenu {
const activeFamilies: string[] = [];
const activeSizes: string[] = [];
- if (this.TextView.props.isSelected()) {
+ 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);
@@ -279,7 +281,7 @@ export default class RichTextMenu extends AntimodeMenu {
//finds all active marks on selection in given group
getActiveMarksOnSelection() {
let activeMarks: MarkType[] = [];
- if (!this.view || !this.TextView.props.isSelected()) return activeMarks;
+ 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);
@@ -317,7 +319,7 @@ export default class RichTextMenu extends AntimodeMenu {
}
destroy() {
- this.fadeOut(true);
+ !this.TextView?.props.isSelected(true) && this.fadeOut(true);
}
@action
@@ -357,9 +359,11 @@ 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>
);
}
@@ -378,7 +382,7 @@ export default class RichTextMenu extends AntimodeMenu {
self.TextView.endUndoTypingBatch();
options.forEach(({ label, mark, command }) => {
if (e.target.value === label && mark) {
- if (!self.TextView.props.isSelected()) {
+ 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;
@@ -388,7 +392,9 @@ export default class RichTextMenu extends AntimodeMenu {
}
});
}
- return <select onChange={onChange} value={activeOption} 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, setter: (val: string) => {}): JSX.Element {
@@ -405,14 +411,17 @@ export default class RichTextMenu extends AntimodeMenu {
self.TextView.endUndoTypingBatch();
options.forEach(({ label, node, command }) => {
if (val === label && node) {
- if (self.TextView.props.isSelected()) {
+ if (self.TextView.props.isSelected(true)) {
UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
setter(val);
}
}
});
}
- return <select value={activeOption} 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) => {
@@ -431,7 +440,7 @@ export default class RichTextMenu extends AntimodeMenu {
const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
let inList: any = undefined;
let fromList = -1;
- let path: any = Array.from((this.view.state.selection.$from as any).path);
+ 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];
@@ -469,13 +478,13 @@ export default class RichTextMenu extends AntimodeMenu {
return true;
}
alignCenter = (state: EditorState<any>, dispatch: any) => {
- return this.TextView.props.isSelected() && this.alignParagraphs(state, "center", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);
}
alignLeft = (state: EditorState<any>, dispatch: any) => {
- return this.TextView.props.isSelected() && this.alignParagraphs(state, "left", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);
}
alignRight = (state: EditorState<any>, dispatch: any) => {
- return this.TextView.props.isSelected() && 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) {
@@ -595,10 +604,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">
@@ -647,7 +657,7 @@ export default class RichTextMenu extends AntimodeMenu {
@action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
@action setActiveColor(color: string) { this.activeFontColor = color; }
- get TextView() { return (this.view as any).TextView as FormattedTextBox; }
+ get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
createColorButton() {
const self = this;
@@ -667,11 +677,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" >
@@ -720,11 +731,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">
@@ -763,7 +775,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">
@@ -774,9 +788,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() {
@@ -819,6 +831,8 @@ export default class RichTextMenu extends AntimodeMenu {
((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);
}
+ @undoBatch
+ @action
deleteLink = () => {
if (this.view) {
const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
@@ -935,7 +949,7 @@ export default class RichTextMenu extends AntimodeMenu {
{[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)),
this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)),
<div className="richTextMenu-divider" key="divider 4" />,
- this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes", action((val: string) => this.activeListType = val)),
+ 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),
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index a989abd6a..33a080fe4 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -54,6 +54,8 @@ export class DashDocView {
this._dashSpan.style.height = node.attrs.height;
this._dashSpan.style.position = "absolute";
this._dashSpan.style.display = "inline-block";
+ this._dashSpan.style.left = "0";
+ this._dashSpan.style.top = "0";
this._dashSpan.style.whiteSpace = "normal";
this._dashSpan.onpointerleave = () => {
@@ -160,9 +162,15 @@ export class DashDocView {
if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+ if (getPos() !== undefined) {
+ const node = view.state.tr.doc.nodeAt(getPos());
+ if (node.attrs.width !== dashDoc._width + "px" ||
+ node.attrs.height !== dashDoc._height + "px") {
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+ }
+ }
} catch (e) {
- console.log(e);
+ console.log("RichTextSchema: " + e);
}
}
};
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 3d7d71b14..f95f46104 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -258,9 +258,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
parseDOM: [{ style: 'background: yellow' }],
toDOM(node: any) {
- return ['span', {
- style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
- }];
+ return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }];
}
},
@@ -277,8 +275,8 @@ export const marks: { [index: string]: MarkSpec } = {
const min = Math.round(node.attrs.modified / 12);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
- return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
+ const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
+ return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];
}
},
// the id of the user who entered the text
@@ -292,7 +290,7 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
toDOM(node: any) {
const uid = node.attrs.userid.replace(".", "").replace("@", "");
- return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
+ return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0];
}
},
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 00c56d73e..6592c488b 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -47,7 +47,7 @@ export default class PDFMenu extends AntimodeMenu {
public AddTag: (key: string, value: string) => boolean = returnFalse;
public PinToPres: () => void = unimplementedFunction;
public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
- public get Active() { return this._opacity ? true : false; }
+ public get Active() { return this._left > 0; }
constructor(props: Readonly<{}>) {
super(props);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4e5fdbfbb..5ed842ab7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -262,7 +262,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
const eventBus = new PDFJSViewer.EventBus(true);
eventBus._on("pagesinit", this.pagesinit);
eventBus._on("pagerendered", action(() => this._showCover = this._showWaiting = false));
- const pdfLinkService = new PDFJSViewer.PDFLinkService();
+ const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
container: this._mainCont.current,
@@ -392,7 +392,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
search = (searchString: string, fwd: boolean, clear: boolean = false) => {
if (clear) {
- this._pdfViewer.findController.executeCommand('reset', {});
+ this._pdfViewer.findController.executeCommand('reset', { query: "" });
} else if (!searchString) {
fwd ? this.nextAnnotation() : this.prevAnnotation();
} else if (this._pdfViewer.pageViewsReady) {
@@ -521,7 +521,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
if (this._marqueeing) {
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
- if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation.
+ if (marquees?.length) { // copy the marquee and convert it to a permanent annotation.
const style = (marquees[0] as HTMLDivElement).style;
const copy = document.createElement("div");
copy.style.left = style.left;
@@ -544,7 +544,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
else {
const sel = window.getSelection();
- if (sel && sel.type === "Range") {
+ if (sel?.type === "Range") {
const selRange = sel.getRangeAt(0);
this.createTextAnnotation(sel, selRange);
PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -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);
@@ -726,7 +726,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
}
-interface PdfViewerMarqueeProps {
+export interface PdfViewerMarqueeProps {
isMarqueeing: () => boolean;
width: () => number;
height: () => number;
@@ -735,7 +735,7 @@ interface PdfViewerMarqueeProps {
}
@observer
-class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
+export class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
render() {
return !this.props.isMarqueeing() ? (null) : <div className="pdfViewerDash-dragAnnotationBox"
style={{
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 4b53963a5..eb61f9a14 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -352,7 +352,6 @@ export class FilterBox extends React.Component {
getDataStatus() { return this._deletedDocsStatus; }
getActiveFilters() {
- console.log(this._authorFieldStatus, this._titleFieldStatus, this._dataFieldStatus);
return (
<div className="active-filters">
{!this._basicWordStatus ? <div className="active-icon container">
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index c9d29e485..99fa6da21 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -125,7 +125,7 @@ export class SearchBox extends React.Component<SearchProps> {
return returnedUri;
} catch (e) {
- console.log(e);
+ console.log("SearchBox:" + e);
}
}
@@ -582,7 +582,6 @@ export class SearchBox extends React.Component<SearchProps> {
}
expandSection(thing: string) {
- console.log("expand");
const element = document.getElementById(thing)!;
// get the height of the element's inner content, regardless of its actual size
const sectionHeight = element.scrollHeight;
@@ -593,7 +592,6 @@ export class SearchBox extends React.Component<SearchProps> {
// when the next css transition finishes (which should be the one we just triggered)
element.addEventListener('transitionend', function handler(e) {
// remove this event listener so it only gets triggered once
- console.log("autoset");
element.removeEventListener('transitionend', handler);
// remove "height" from the element's inline styles, so it can return to its initial value
@@ -608,7 +606,6 @@ export class SearchBox extends React.Component<SearchProps> {
autoset(thing: string) {
const element = document.getElementById(thing)!;
- console.log("autoset");
element.removeEventListener('transitionend', function (e) { });
// remove "height" from the element's inline styles, so it can return to its initial value
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index e4d7f2fd5..466822eba 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -58,7 +58,6 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
this._forwardTimeline.play();
this._forwardTimeline.reverse();
this.props.handleChange();
- console.log(this.props.getStatus());
}
@action.bound
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7aa1d528d..87acb2ea7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,6 +1,6 @@
-import { action, computed, observable, ObservableMap, runInAction } from "mobx";
+import { action, computed, observable, ObservableMap, runInAction, untracked } from "mobx";
import { computedFn } from "mobx-utils";
-import { alias, map, serializable } from "serializr";
+import { alias, map, serializable, list } from "serializr";
import { DocServer } from "../client/DocServer";
import { DocumentType } from "../client/documents/DocumentTypes";
import { Scripting, scriptingGlobal } from "../client/util/Scripting";
@@ -14,11 +14,15 @@ import { ObjectField } from "./ObjectField";
import { PrefetchProxy, ProxyField } from "./Proxy";
import { FieldId, RefField } from "./RefField";
import { RichTextField } from "./RichTextField";
+import { ImageField, VideoField, WebField, AudioField, PdfField } from "./URLField";
+import { DateField } from "./DateField";
import { listSpec } from "./Schema";
-import { ComputedField } from "./ScriptField";
+import { ComputedField, ScriptField } from "./ScriptField";
import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions } from "./util";
import { LinkManager } from "../client/util/LinkManager";
+import JSZip = require("jszip");
+import { saveAs } from "file-saver";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -92,31 +96,33 @@ export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
export const DataSym = Symbol("Data");
export const LayoutSym = Symbol("Layout");
+export const FieldsSym = Symbol("Fields");
export const AclSym = Symbol("Acl");
export const AclPrivate = Symbol("AclOwnerOnly");
export const AclReadonly = Symbol("AclReadOnly");
export const AclAddonly = Symbol("AclAddonly");
-export const AclReadWrite = Symbol("AclReadWrite");
+export const AclEdit = Symbol("AclEdit");
+export const AclAdmin = Symbol("AclAdmin");
export const UpdatingFromServer = Symbol("UpdatingFromServer");
-const CachedUpdates = Symbol("Cached updates");
+export const CachedUpdates = Symbol("Cached updates");
+const AclMap = new Map<string, symbol>([
+ [SharingPermissions.None, AclPrivate],
+ [SharingPermissions.View, AclReadonly],
+ [SharingPermissions.Add, AclAddonly],
+ [SharingPermissions.Edit, AclEdit],
+ [SharingPermissions.Admin, AclAdmin]
+]);
export function fetchProto(doc: Doc) {
- if (doc.author !== Doc.CurrentUserEmail) {
- const acl = Doc.Get(doc, "ACL", true);
- switch (acl) {
- case "ownerOnly":
- doc[AclSym] = AclPrivate;
- return undefined;
- case "readOnly":
- doc[AclSym] = AclReadonly;
- break;
- case "addOnly":
- doc[AclSym] = AclAddonly;
- break;
- case "write":
- doc[AclSym] = AclReadWrite;
- }
+ const permissions: { [key: string]: symbol } = {};
+
+ Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
+
+ if (Object.keys(permissions).length) doc[AclSym] = permissions;
+
+ if (GetEffectiveAcl(doc) === AclPrivate) {
+ runInAction(() => doc[FieldsSym](true));
}
if (doc.proto instanceof Promise) {
@@ -134,10 +140,10 @@ export class Doc extends RefField {
set: setter,
get: getter,
// getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
- has: (target, key) => target[AclSym] !== AclPrivate && key in target.__fields,
+ has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
ownKeys: target => {
const obj = {} as any;
- if (target[AclSym] !== AclPrivate) Object.assign(obj, target.___fields);
+ if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
return Object.keys(obj);
},
@@ -145,11 +151,11 @@ export class Doc extends RefField {
if (prop.toString() === "__LAYOUT__") {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
- if (prop in target.__fields) {
+ if (prop in target.__fieldKeys) {
return {
configurable: true,//TODO Should configurable be true?
enumerable: true,
- value: target.__fields[prop]
+ value: 0//() => target.__fields[prop])
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
@@ -173,16 +179,23 @@ export class Doc extends RefField {
this.___fields = value;
for (const key in value) {
const field = value[key];
+ field && (this.__fieldKeys[key] = true);
if (!(field instanceof ObjectField)) continue;
field[Parent] = this[Self];
field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
}
}
+ private get __fieldKeys() { return this.___fieldKeys; }
+ private set __fieldKeys(value) {
+ this.___fieldKeys = value;
+ }
@observable
- //{ [key: string]: Field | FieldWaiting | undefined }
private ___fields: any = {};
+ @observable
+ private ___fieldKeys: any = {};
+
private [UpdatingFromServer]: boolean = false;
private [Update] = (diff: any) => {
@@ -191,11 +204,19 @@ export class Doc extends RefField {
private [Self] = this;
private [SelfProxy]: any;
- public [AclSym]: any = undefined;
+ public [FieldsSym] = (clear?: boolean) => {
+ if (clear) {
+ this.___fields = {};
+ this.___fieldKeys = {};
+ }
+ return this.___fields;
+ }
+ @observable
+ public [AclSym]: { [key: string]: symbol };
public [WidthSym] = () => NumCast(this[SelfProxy]._width);
public [HeightSym] = () => NumCast(this[SelfProxy]._height);
public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
- public [ToString]() { return `Doc(${this[AclSym] === AclPrivate ? "-inaccessible-" : this.title})`; }
+ public [ToString]() { return `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
public get [DataSym]() {
const self = this[SelfProxy];
@@ -215,8 +236,8 @@ export class Doc extends RefField {
return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
}
return undefined;
- }
+ }
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
public static CurrentUserEmail: string = "";
@@ -231,11 +252,18 @@ export class Doc extends RefField {
const fKey = key.substring(7);
const fn = async () => {
const value = await SerializationHelper.Deserialize(set[key]);
+ const prev = GetEffectiveAcl(this);
this[UpdatingFromServer] = true;
this[fKey] = value;
+ if (fKey.startsWith("ACL")) {
+ fetchProto(this);
+ }
this[UpdatingFromServer] = false;
+ if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
+ DocServer.GetRefField(this[Id], true);
+ }
};
- if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
await fn();
} else {
@@ -483,27 +511,28 @@ export namespace Doc {
return alias;
}
-
-
- export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc {
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean): Promise<Doc> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
- const copy = new Doc(undefined, true);
+ const copy = dontCreate ? doc : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
- const exclude = Cast(doc.excludeFields, listSpec("string"), []);
- Object.keys(doc).forEach(key => {
- if (exclude.includes(key)) return;
+ const filter = Cast(doc.cloneFieldFilter, listSpec("string"), exclusions);
+ await Promise.all(Object.keys(doc).map(async key => {
+ if (filter.includes(key)) return;
+ const assignKey = (val: any) => !dontCreate && (copy[key] = val);
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = (field: ObjectField) => {
- const list = Cast(doc[key], listSpec(Doc));
- if (list !== undefined && !(list instanceof Promise)) {
- copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs)));
+ const copyObjectField = async (field: ObjectField) => {
+ const list = await Cast(doc[key], listSpec(Doc));
+ const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
+ if (docs !== undefined && docs.length) {
+ const clones = await Promise.all(docs.map(async d => await Doc.makeClone(d as Doc, cloneMap, rtfs, exclusions, dontCreate)));
+ !dontCreate && assignKey(new List<Doc>(clones));
} else if (doc[key] instanceof Doc) {
- copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields
+ assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions, dontCreate)); // reference documents except copy documents that are expanded teplate fields
} else {
- copy[key] = ObjectField.MakeCopy(field);
+ assignKey(ObjectField.MakeCopy(field));
if (field instanceof RichTextField) {
if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
rtfs.push({ copy, key, field });
@@ -513,32 +542,34 @@ export namespace Doc {
};
if (key === "proto") {
if (doc[key] instanceof Doc) {
- copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs);
+ assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate));
}
} else {
if (field instanceof RefField) {
- copy[key] = field;
+ assignKey(field);
} else if (cfield instanceof ComputedField) {
- copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
- (key === "links" && field instanceof ObjectField) && copyObjectField(field);
+ !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
+ (key === "links" && field instanceof ObjectField) && await copyObjectField(field);
} else if (field instanceof ObjectField) {
- copyObjectField(field);
+ await copyObjectField(field);
} else if (field instanceof Promise) {
debugger; //This shouldn't happend...
} else {
- copy[key] = field;
+ assignKey(field);
}
}
- });
- Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
- copy.cloneOf = doc;
- cloneMap.set(doc[Id], copy);
+ }));
+ if (!dontCreate) {
+ Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
+ copy.cloneOf = doc;
+ cloneMap.set(doc[Id], copy);
+ }
return copy;
}
- export function MakeClone(doc: Doc): Doc {
+ export async function MakeClone(doc: Doc, dontCreate: boolean = false) {
const cloneMap = new Map<string, Doc>();
const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = Doc.makeClone(doc, cloneMap, rtfMap);
+ const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"], dontCreate);
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
@@ -552,9 +583,56 @@ export namespace Doc {
const re = new RegExp(regex, "g");
copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
- return copy;
+ return { clone: copy, map: cloneMap };
}
+ export async function Zip(doc: Doc) {
+ const { clone, map } = await Doc.MakeClone(doc, true);
+ function replacer(key: any, value: any) {
+ if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
+ else if (value instanceof Doc) {
+ if (key !== "field" && Number.isNaN(Number(key))) {
+ const __fields = value[FieldsSym]();
+ return { id: value[Id], __type: "Doc", fields: __fields };
+ } else {
+ return { fieldId: value[Id], __type: "proxy" };
+ }
+ }
+ else if (value instanceof ScriptField) return { script: value.script, __type: "script" };
+ else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };
+ else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };
+ else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" };
+ else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" };
+ else if (value instanceof VideoField) return { url: value.url.href, __type: "video" };
+ else if (value instanceof WebField) return { url: value.url.href, __type: "web" };
+ else if (value instanceof DateField) return { date: value.toString(), __type: "date" };
+ else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" };
+ else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" };
+ else if (value instanceof ComputedField) return { script: value.script, __type: "computed" };
+ else return value;
+ }
+
+ const docs: { [id: string]: any } = {};
+ Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]);
+ const docString = JSON.stringify({ id: doc[Id], docs }, replacer);
+
+ var zip = new JSZip();
+
+ zip.file("doc.json", docString);
+
+ // // Generate a directory within the Zip file structure
+ // var img = zip.folder("images");
+
+ // // Add a file to the directory, in this case an image with data URI as contents
+ // img.file("smile.gif", imgData, {base64: true});
+
+ // Generate the zip file asynchronously
+ zip.generateAsync({ type: "blob" })
+ .then((content: any) => {
+ // Force down of the Zip file
+ saveAs(content, "download.zip");
+ });
+ }
//
// Determines whether the layout needs to be expanded (as a template).
// template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template
@@ -657,7 +735,7 @@ export namespace Doc {
export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
const copy = new Doc(copyProtoId, true);
- const exclude = Cast(doc.excludeFields, listSpec("string"), []);
+ const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
Object.keys(doc).forEach(key => {
if (exclude.includes(key)) return;
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
@@ -823,7 +901,7 @@ export namespace Doc {
}
// don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
export function IsBrushedDegreeUnmemoized(doc: Doc) {
- if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return 0;
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
}
export function IsBrushedDegree(doc: Doc) {
@@ -832,15 +910,13 @@ export namespace Doc {
})(doc);
}
export function BrushDoc(doc: Doc) {
-
- if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
brushManager.BrushedDoc.set(doc, true);
brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
return doc;
}
export function UnBrushDoc(doc: Doc) {
-
- if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
brushManager.BrushedDoc.delete(doc);
brushManager.BrushedDoc.delete(Doc.GetProto(doc));
return doc;
@@ -870,7 +946,7 @@ export namespace Doc {
}
const highlightManager = new HighlightBrush();
export function IsHighlighted(doc: Doc) {
- if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return false;
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
}
export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
@@ -899,9 +975,12 @@ export namespace Doc {
}
export function getDocTemplate(doc?: Doc) {
- return doc?.isTemplateDoc ? doc :
- Cast(doc?.dragFactory, Doc, null)?.isTemplateDoc ? doc?.dragFactory :
- Cast(doc?.layout, Doc, null)?.isTemplateDoc ? doc?.layout : undefined;
+ return !doc ? undefined :
+ doc.isTemplateDoc ? doc :
+ Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory :
+ Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ?
+ (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) :
+ undefined;
}
export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
@@ -1156,7 +1235,7 @@ Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
-Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
+Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
Scripting.addGlobal(function aliasDocs(field: any) { return Doc.aliasDocs(field); });
Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 555faaad0..62734d3d2 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -9,7 +9,12 @@ import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols";
import { scriptingGlobal } from "../client/util/Scripting";
import { Plugins } from "./util";
-@Deserializable("proxy")
+function deserializeProxy(field: any) {
+ if (!field.cache) {
+ field.cache = DocServer.GetCachedRefField(field.fieldId) as any;
+ }
+}
+@Deserializable("proxy", deserializeProxy)
export class ProxyField<T extends RefField> extends ObjectField {
constructor();
constructor(value: T);
@@ -17,6 +22,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
constructor(value?: T | string) {
super();
if (typeof value === "string") {
+ this.cache = DocServer.GetCachedRefField(value) as any;
this.fieldId = value;
} else if (value) {
this.cache = value;
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 98ef3e087..23ac50f74 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -31,7 +31,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
}
const proto = new Proxy({}, {
get(target: any, prop, receiver) {
- const field = receiver.doc[prop];
+ const field = receiver.doc?.[prop];
if (prop in schema) {
const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec
@@ -52,7 +52,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
return field;
},
set(target: any, prop, value, receiver) {
- receiver.doc[prop] = value;
+ receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in ACLs
return true;
}
});
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 11b3b0524..bd08b2f32 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -3,7 +3,7 @@ import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions, CompileE
import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols";
import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Doc, Field } from "./Doc";
+import { Doc, Field, Opt } from "./Doc";
import { Plugins, setter } from "./util";
import { computedFn } from "mobx-utils";
import { ProxyField } from "./Proxy";
@@ -38,6 +38,21 @@ const scriptSchema = createSimpleSchema({
});
async function deserializeScript(script: ScriptField) {
+ if (script.script.originalScript === 'getCopy(this.dragFactory, true)') {
+ return (script as any).script = (ScriptField.GetCopyOfDragFactory ?? (ScriptField.GetCopyOfDragFactory = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')))?.script;
+ }
+ if (script.script.originalScript === 'links(self)') {
+ return (script as any).script = (ScriptField.LinksSelf ?? (ScriptField.LinksSelf = ComputedField.MakeFunction('links(self)')))?.script;
+ }
+ if (script.script.originalScript === 'openOnRight(getCopy(this.dragFactory, true))') {
+ return (script as any).script = (ScriptField.OpenOnRight ?? (ScriptField.OpenOnRight = ComputedField.MakeFunction('openOnRight(getCopy(this.dragFactory, true))')))?.script;
+ }
+ if (script.script.originalScript === 'deiconifyView(self)') {
+ return (script as any).script = (ScriptField.DeiconifyView ?? (ScriptField.DeiconifyView = ComputedField.MakeFunction('deiconifyView(self)')))?.script;
+ }
+ if (script.script.originalScript === 'convertToButtons(dragData)') {
+ return (script as any).script = (ScriptField.ConvertToButtons ?? (ScriptField.ConvertToButtons = ComputedField.MakeFunction('convertToButtons(dragData)', { dragData: "DocumentDragData" })))?.script;
+ }
const captures: ProxyField<Doc> = (script as any).captures;
if (captures) {
const doc = (await captures.value())!;
@@ -65,6 +80,11 @@ export class ScriptField extends ObjectField {
@serializable(autoObject())
private captures?: ProxyField<Doc>;
+ public static GetCopyOfDragFactory: Opt<ScriptField>;
+ public static LinksSelf: Opt<ScriptField>;
+ public static OpenOnRight: Opt<ScriptField>;
+ public static DeiconifyView: Opt<ScriptField>;
+ public static ConvertToButtons: Opt<ScriptField>;
constructor(script: CompiledScript, setterscript?: CompiledScript) {
super();
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 61a37ef8a..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
@@ -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
@@ -88,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/fields/util.ts b/src/fields/util.ts
index 2dc21c987..a62795e64 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,14 +1,15 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action, trace } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
import { ComputedField } from "./ScriptField";
-import { ScriptCast } from "./Types";
+import { ScriptCast, StrCast } from "./Types";
+import { returnZero } from "../Utils";
function _readOnlySetter(): never {
@@ -34,7 +35,6 @@ export namespace Plugins {
}
const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
- //console.log("-set " + target[SelfProxy].title + "(" + target[SelfProxy][prop] + ")." + prop.toString() + " = " + value);
if (SerializationHelper.IsSerializing()) {
target[prop] = value;
return true;
@@ -67,18 +67,25 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
delete curValue[Parent];
delete curValue[OnUpdate];
}
+
+ const effectiveAcl = GetEffectiveAcl(target);
+
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
- const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default);
+ const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
+
if (writeToDoc) {
if (value === undefined) {
+ target.__fieldKeys && (delete target.__fieldKeys[prop]);
delete target.__fields[prop];
} else {
+ target.__fieldKeys && (target.__fieldKeys[prop] = true);
target.__fields[prop] = value;
}
//if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
+
if (writeToServer) {
if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
@@ -89,8 +96,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
redo: () => receiver[prop] = value,
undo: () => receiver[prop] = curValue
});
+ return true;
}
- return true;
+ return false;
});
let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl;
@@ -107,11 +115,117 @@ export function OVERRIDE_ACL(val: boolean) {
_overrideAcl = val;
}
+let playgroundMode = false;
+
+export function togglePlaygroundMode() {
+ playgroundMode = !playgroundMode;
+}
+
+export function getPlaygroundMode() {
+ return playgroundMode;
+}
+
+let currentUserGroups: string[] = [];
+
+export function setGroups(groups: string[]) {
+ currentUserGroups = groups;
+}
+
+export enum SharingPermissions {
+ Admin = "Admin",
+ Edit = "Can Edit",
+ Add = "Can Add",
+ View = "Can View",
+ None = "Not Shared"
+}
+
+export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
+ if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
+
+ if (target[AclSym] && Object.keys(target[AclSym]).length) {
+
+ if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;
+
+ if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
+
+ let effectiveAcl = AclPrivate;
+ let aclPresent = false;
+
+ const HierarchyMapping = new Map<symbol, number>([
+ [AclPrivate, 0],
+ [AclReadonly, 1],
+ [AclAddonly, 2],
+ [AclEdit, 3],
+ [AclAdmin, 4]
+ ]);
+
+ for (const [key, value] of Object.entries(target[AclSym])) {
+ if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
+ if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
+ aclPresent = true;
+ effectiveAcl = value as symbol;
+ if (effectiveAcl === AclEdit) break;
+ }
+ }
+ }
+ return aclPresent ? effectiveAcl : AclEdit;
+ }
+ return AclAdmin;
+}
+
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
+
+ const HierarchyMapping = new Map<string, number>([
+ ["Not Shared", 0],
+ ["Can View", 1],
+ ["Can Add", 2],
+ ["Can Edit", 3],
+ ["Admin", 4]
+ ]);
+
+ const dataDoc = target[DataSym];
+
+ if (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!) target[key] = acl;
+
+ if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
+ dataDoc[key] = acl;
+
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
+ if (d.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
+ distributeAcls(key, acl, d);
+ d[key] = acl;
+ }
+ const data = d[DataSym];
+ if (data && data.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
+ distributeAcls(key, acl, data);
+ data[key] = acl;
+ }
+ });
+
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
+ if (d.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
+ distributeAcls(key, acl, d);
+ d[key] = acl;
+ }
+ const data = d[DataSym];
+ if (data && data.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
+ distributeAcls(key, acl, data);
+ data[key] = acl;
+ }
+ });
+ }
+}
+
const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
"chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
- if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true;
+ const effectiveAcl = GetEffectiveAcl(target, in_prop);
+ if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
+
+ if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
+ // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
+
if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
if (!prop.startsWith("_")) {
console.log(prop + " is deprecated - switch to _" + prop);
@@ -130,8 +244,10 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
let prop = in_prop;
+
+ if (in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];
if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
- if (target[AclSym] === AclPrivate && !_overrideAcl) return undefined;
+ if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
if (prop === LayoutSym) {
return target.__LAYOUT__;
}
@@ -168,7 +284,7 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
}
if (field === undefined && !ignoreProto && prop !== "proto") {
const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
- if (proto instanceof Doc && proto[AclSym] !== AclPrivate) {
+ if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 590bf8f9d..b75102e43 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -143,6 +143,7 @@ export class AudioUpload extends React.Component {
interactive={true}
dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
overlayDisplayedOpacity={this.overlayOpacity}
+ closeOnExternalClick={this.closeUpload}
/>
);
}
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 5ea626d52..c6420bab5 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -55,7 +55,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
doc = Docs.Create.VideoDocument(path, { _nativeWidth: 400, _width: 400, title: name });
// Case 2: File is a PDF document
} else if (file.type === "application/pdf") {
- doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, _fitWidth: true, title: name });
// Case 3: File is another document type (most likely Image)
} else {
doc = Docs.Create.ImageDocument(path, { _nativeWidth: 400, _width: 400, title: name });
@@ -172,6 +172,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
interactive={true}
dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
overlayDisplayedOpacity={this.overlayOpacity}
+ closeOnExternalClick={this.closeUpload}
/>
);
}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index fafa94a11..3a889b0db 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -28,7 +28,6 @@ import { DockedFrameRenderer } from '../client/views/collections/CollectionDocki
import { InkTool } from '../fields/InkField';
import GestureOverlay from "../client/views/GestureOverlay";
import { ScriptField } from "../fields/ScriptField";
-import InkOptionsMenu from "../client/views/collections/collectionFreeForm/InkOptionsMenu";
import { RadialMenu } from "../client/views/nodes/RadialMenu";
import { UndoManager } from "../client/util/UndoManager";
import { List } from "../fields/List";
@@ -38,6 +37,7 @@ import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
import { AudioBox } from "../client/views/nodes/AudioBox";
import { CollectionViewType } from "../client/views/collections/CollectionView";
import { DocumentType } from "../client/documents/DocumentTypes";
+import { CollectionFreeFormViewChrome } from "../client/views/collections/CollectionMenu";
library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -429,7 +429,7 @@ export class MobileInterface extends React.Component {
@computed get inkMenu() {
return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? (null) :
<div className="colorSelector">
- <InkOptionsMenu />
+ {/* <CollectionFreeFormViewChrome /> */}
</div>;
}
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index c5f3ca717..0d4472fdc 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -80,20 +80,14 @@ async function getDocs(id: string) {
}
const ids: string[] = [];
for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) {
- continue;
- }
+ if (!doc.fields.hasOwnProperty(key)) { continue; }
const field = doc.fields[key];
- if (field === undefined || field === null) {
- continue;
- }
+ if (field === undefined || field === null) { continue; }
if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
ids.push(field.fieldId);
} else if (field.__type === "script" || field.__type === "computed") {
- if (field.captures) {
- ids.push(field.captures.fieldId);
- }
+ field.captures && ids.push(field.captures.fieldId);
} else if (field.__type === "list") {
ids.push(...fn(field));
} else if (typeof field === "string") {
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 55ceab9fb..4455d11eb 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -16,6 +16,7 @@ const imageDataUri = require('image-data-uri');
import { isWebUri } from "valid-url";
import { launch } from "puppeteer";
import { Opt } from "../../fields/Doc";
+import { SolrManager } from "./SearchManager";
export enum Directory {
parsed_files = "parsed_files",
@@ -139,13 +140,9 @@ export default class UploadManager extends ApiManager {
doc.id = getId(doc.id);
}
for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) {
- continue;
- }
+ if (!doc.fields.hasOwnProperty(key)) { continue; }
const field = doc.fields[key];
- if (field === undefined || field === null) {
- continue;
- }
+ if (field === undefined || field === null) { continue; }
if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
field.fieldId = getId(field.fieldId);
@@ -208,11 +205,8 @@ export default class UploadManager extends ApiManager {
} catch (e) { console.log(e); }
unlink(path_2, () => { });
}
- if (id) {
- res.send(JSON.stringify(getId(id)));
- } else {
- res.send(JSON.stringify("error"));
- }
+ SolrManager.update();
+ res.send(JSON.stringify(id ? getId(id) : "error"));
} catch (e) { console.log(e); }
resolve();
});
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
index 423ce9b46..935ec3871 100644
--- a/src/server/Recommender.ts
+++ b/src/server/Recommender.ts
@@ -21,7 +21,6 @@
// private choice: string = ""; // Tensorflow or Word2Vec
// constructor() {
-// console.log("creating recommender...");
// Recommender.Instance = this;
// }
@@ -70,7 +69,6 @@
// if (this._model) {
// if (this.choice === "WV") {
// let similarity = this._model.similarity('father', 'mother');
-// console.log(similarity);
// }
// else if (this.choice === "TF") {
// const model = this._model as use.UniversalSentenceEncoder;
@@ -119,7 +117,6 @@
// }
// // public async trainModel() {
-// // console.log("phrasing...");
// // w2v.word2vec("./node_modules/word2vec/examples/eng_news-typical_2016_1M-sentences.txt", './node_modules/word2vec/examples/my_phrases.txt', {
// // cbow: 1,
// // size: 200,
@@ -131,7 +128,6 @@
// // iter: 200,
// // minCount: 2
// // });
-// // console.log("phrased!!!");
// // }
// }
diff --git a/src/server/downsize.ts b/src/server/downsize.ts
index cd0d83812..5cd709fa3 100644
--- a/src/server/downsize.ts
+++ b/src/server/downsize.ts
@@ -7,7 +7,7 @@ const jpgTypes = ["jpg", "JPG", "jpeg", "JPEG"];
const smallResizer = sharp().resize(100);
fs.readdir(folder, async (err, files) => {
if (err) {
- console.log(err);
+ console.log("readdir:" + err);
return;
}
// files.forEach(file => {